codex-local-sdk-python 0.1.0__py3-none-any.whl

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 (46) hide show
  1. codex_local_sdk/__init__.py +34 -0
  2. codex_local_sdk/client.py +1776 -0
  3. codex_local_sdk/exceptions.py +19 -0
  4. codex_local_sdk/models.py +77 -0
  5. codex_local_sdk/session_store.py +447 -0
  6. codex_local_sdk/telemetry.py +24 -0
  7. codex_local_sdk_python-0.1.0.dist-info/METADATA +36 -0
  8. codex_local_sdk_python-0.1.0.dist-info/RECORD +46 -0
  9. codex_local_sdk_python-0.1.0.dist-info/WHEEL +5 -0
  10. codex_local_sdk_python-0.1.0.dist-info/entry_points.txt +2 -0
  11. codex_local_sdk_python-0.1.0.dist-info/top_level.txt +2 -0
  12. codex_sdk_python/__init__.py +5 -0
  13. codex_sdk_python/cli.py +131 -0
  14. codex_sdk_python/data/docs/documentation/AGENTS.md +35 -0
  15. codex_sdk_python/data/docs/documentation/README.md +20 -0
  16. codex_sdk_python/data/docs/documentation/architecture_starter.md +51 -0
  17. codex_sdk_python/data/docs/documentation/automation_ci_cd.md +35 -0
  18. codex_sdk_python/data/docs/documentation/codex_core_docs.md +43 -0
  19. codex_sdk_python/data/docs/documentation/configuration.md +35 -0
  20. codex_sdk_python/data/docs/documentation/enterprise_governance.md +33 -0
  21. codex_sdk_python/data/docs/documentation/mcp_and_skills.md +39 -0
  22. codex_sdk_python/data/docs/documentation/model_pricing_notes.md +24 -0
  23. codex_sdk_python/data/docs/documentation/programmatic_integration.md +30 -0
  24. codex_sdk_python/data/docs/documentation/security_permissions.md +47 -0
  25. codex_sdk_python/data/docs/documentation/setup_auth.md +42 -0
  26. codex_sdk_python/data/docs/html documentation/api-reference.html +218 -0
  27. codex_sdk_python/data/docs/html documentation/assets/styles.css +265 -0
  28. codex_sdk_python/data/docs/html documentation/examples.html +203 -0
  29. codex_sdk_python/data/docs/html documentation/getting-started.html +189 -0
  30. codex_sdk_python/data/docs/html documentation/index.html +161 -0
  31. codex_sdk_python/data/docs/html documentation/live-threads.html +180 -0
  32. codex_sdk_python/data/docs/html documentation/testing-and-quality.html +110 -0
  33. codex_sdk_python/data/skills/codex-local-sdk-usage/SKILL.md +41 -0
  34. codex_sdk_python/data/skills/codex-local-sdk-usage/agents/openai.yaml +4 -0
  35. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_async_run.py +36 -0
  36. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_live_stream.py +28 -0
  37. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_resume_named_session.py +35 -0
  38. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_schema_output.py +41 -0
  39. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_sync_run.py +24 -0
  40. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_telemetry_and_retry.py +56 -0
  41. codex_sdk_python/data/skills/codex-local-sdk-usage/assets/template_thread_session.py +29 -0
  42. codex_sdk_python/data/skills/codex-local-sdk-usage/references/guardrails-and-failure-modes.md +55 -0
  43. codex_sdk_python/data/skills/codex-local-sdk-usage/references/sdk-api-cheatsheet.md +72 -0
  44. codex_sdk_python/data/skills/codex-local-sdk-usage/references/task-routing.md +26 -0
  45. codex_sdk_python/data/skills/codex-local-sdk-usage/scripts/preflight_check.py +132 -0
  46. codex_sdk_python/data/skills/codex-local-sdk-usage/scripts/scaffold_usage_template.py +66 -0
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: codex-local-sdk-usage
3
+ description: Use when Codex needs to write, wire, or troubleshoot Python code that consumes this repository's `codex_local_sdk` package (not modify SDK internals). Trigger for requests to call `CodexLocalClient`, run or resume threads, stream JSON events, configure retries and timeouts, use schema-constrained output, or persist named sessions.
4
+ ---
5
+
6
+ # Codex Local SDK Usage
7
+
8
+ ## Route Quickly
9
+ Load only the reference file needed for the task:
10
+
11
+ - Select an execution path: `references/task-routing.md`
12
+ - Check methods, request flags, and result fields: `references/sdk-api-cheatsheet.md`
13
+ - Debug failures, retries, and session issues: `references/guardrails-and-failure-modes.md`
14
+
15
+ ## Execute Workflow
16
+ 1. Run `scripts/preflight_check.py` to verify Python, package import, and `codex` CLI presence.
17
+ 2. Run `scripts/scaffold_usage_template.py --list` and choose the closest template.
18
+ 3. Run the scaffold command with `--template` and `--output`.
19
+ 4. Replace placeholders (`prompt`, schema, paths, session names, retries) and keep signatures aligned with the API cheat sheet.
20
+ 5. Run the generated script and inspect `CodexExecResult` (`return_code`, `turn_status`, `stderr`, `final_message`).
21
+ 6. If execution fails, apply the guardrails reference and retry.
22
+
23
+ ## Use Bundled Scripts
24
+ - `scripts/preflight_check.py`: print readiness checks with optional strict mode.
25
+ - `scripts/scaffold_usage_template.py`: copy a starter script from `assets/`.
26
+
27
+ ## Use Bundled Templates
28
+ - `assets/template_sync_run.py`
29
+ - `assets/template_live_stream.py`
30
+ - `assets/template_thread_session.py`
31
+ - `assets/template_resume_named_session.py`
32
+ - `assets/template_async_run.py`
33
+ - `assets/template_schema_output.py`
34
+ - `assets/template_telemetry_and_retry.py`
35
+
36
+ ## Keep Consumer Guardrails
37
+ - Set `timeout_seconds` for `run*` and `resume*` unless intentionally unbounded.
38
+ - Set `json_output=True` whenever you need structured events, `thread_id`, or `turn_status`.
39
+ - Catch `CodexExecFailedError` when `raise_on_error=True`; inspect `exc.result`.
40
+ - Pass `session_id` or `session_name`, never both.
41
+ - Treat live methods (`run_live*`, `resume_live*`) as startup-retry only; once a handle is returned, caller code owns interruption/retry logic.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Codex Local SDK Usage"
3
+ short_description: "Build scripts that consume Codex Local SDK APIs"
4
+ default_prompt: "Use $codex-local-sdk-usage to build or troubleshoot code that uses codex_local_sdk APIs."
@@ -0,0 +1,36 @@
1
+ """Template: async run and async live streaming."""
2
+
3
+ import asyncio
4
+
5
+ from codex_local_sdk import CodexExecRequest, CodexLocalClient, SandboxMode
6
+
7
+
8
+ async def main() -> None:
9
+ client = CodexLocalClient()
10
+
11
+ result = await client.run_async(
12
+ CodexExecRequest(
13
+ prompt="TODO: replace with your prompt",
14
+ sandbox=SandboxMode.READ_ONLY,
15
+ ),
16
+ timeout_seconds=120,
17
+ )
18
+ print("async return_code:", result.return_code)
19
+ print("async final_message:\n", result.final_message)
20
+
21
+ live = await client.run_live_async(
22
+ CodexExecRequest(
23
+ prompt="TODO: streaming prompt",
24
+ sandbox=SandboxMode.READ_ONLY,
25
+ json_output=True,
26
+ )
27
+ )
28
+ async for event in live.iter_events():
29
+ print("event:", event.type)
30
+
31
+ final = await live.wait()
32
+ print("live turn_status:", final.turn_status)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ asyncio.run(main())
@@ -0,0 +1,28 @@
1
+ """Template: live JSON event streaming with CodexLocalClient.run_live."""
2
+
3
+ from codex_local_sdk import CodexExecRequest, CodexLocalClient, SandboxMode
4
+
5
+
6
+ def main() -> None:
7
+ client = CodexLocalClient()
8
+
9
+ live = client.run_live(
10
+ CodexExecRequest(
11
+ prompt="TODO: replace with your prompt",
12
+ sandbox=SandboxMode.READ_ONLY,
13
+ json_output=True,
14
+ )
15
+ )
16
+
17
+ print("pid:", live.pid)
18
+ for event in live.iter_events():
19
+ print("event:", event.type)
20
+
21
+ result = live.wait()
22
+ print("return_code:", result.return_code)
23
+ print("turn_status:", result.turn_status)
24
+ print("final_message:\n", result.final_message)
25
+
26
+
27
+ if __name__ == "__main__":
28
+ main()
@@ -0,0 +1,35 @@
1
+ """Template: persist and resume a named session using JsonFileSessionStore."""
2
+
3
+ from codex_local_sdk import CodexLocalClient, JsonFileSessionStore, SandboxMode
4
+
5
+
6
+ def main() -> None:
7
+ store = JsonFileSessionStore(".codex_sessions.json")
8
+ client = CodexLocalClient(session_store=store)
9
+
10
+ session_name = "todo-session"
11
+
12
+ if client.get_session_id(session_name):
13
+ result = client.resume(
14
+ prompt="TODO: continue prompt",
15
+ session_name=session_name,
16
+ last=False,
17
+ json_output=True,
18
+ timeout_seconds=120,
19
+ )
20
+ print("resumed turn status:", result.turn_status)
21
+ print("resumed final message:\n", result.final_message)
22
+ return
23
+
24
+ session, first = client.start_thread(
25
+ prompt="TODO: initial prompt",
26
+ sandbox=SandboxMode.READ_ONLY,
27
+ session_name=session_name,
28
+ timeout_seconds=120,
29
+ )
30
+ print("created session:", session.session_name, session.session_id)
31
+ print("first turn status:", first.turn_status)
32
+
33
+
34
+ if __name__ == "__main__":
35
+ main()
@@ -0,0 +1,41 @@
1
+ """Template: schema-constrained output with run_with_schema."""
2
+
3
+ import json
4
+
5
+ from codex_local_sdk import CodexLocalClient, SandboxMode
6
+
7
+
8
+ def main() -> None:
9
+ client = CodexLocalClient()
10
+
11
+ schema = {
12
+ "type": "object",
13
+ "properties": {
14
+ "summary": {"type": "string"},
15
+ "actions": {
16
+ "type": "array",
17
+ "items": {"type": "string"},
18
+ },
19
+ },
20
+ "required": ["summary", "actions"],
21
+ "additionalProperties": False,
22
+ }
23
+
24
+ result = client.run_with_schema(
25
+ prompt="TODO: replace with your prompt",
26
+ schema=schema,
27
+ output_json_path="structured_output.json",
28
+ sandbox=SandboxMode.READ_ONLY,
29
+ timeout_seconds=120,
30
+ )
31
+
32
+ print("return_code:", result.return_code)
33
+ print("final_message:\n", result.final_message)
34
+
35
+ if result.final_message:
36
+ parsed = json.loads(result.final_message)
37
+ print("parsed keys:", sorted(parsed.keys()))
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main()
@@ -0,0 +1,24 @@
1
+ """Template: synchronous one-shot execution with CodexLocalClient.run."""
2
+
3
+ from codex_local_sdk import CodexExecRequest, CodexLocalClient, SandboxMode
4
+
5
+
6
+ def main() -> None:
7
+ client = CodexLocalClient()
8
+
9
+ request = CodexExecRequest(
10
+ prompt="TODO: replace with your prompt",
11
+ sandbox=SandboxMode.READ_ONLY,
12
+ json_output=True,
13
+ )
14
+
15
+ result = client.run(request, timeout_seconds=120)
16
+
17
+ print("return_code:", result.return_code)
18
+ print("turn_status:", result.turn_status)
19
+ print("thread_id:", result.thread_id)
20
+ print("final_message:\n", result.final_message)
21
+
22
+
23
+ if __name__ == "__main__":
24
+ main()
@@ -0,0 +1,56 @@
1
+ """Template: telemetry hook plus retry policy for robust sync execution."""
2
+
3
+ from codex_local_sdk import (
4
+ CodexClientEvent,
5
+ CodexExecFailedError,
6
+ CodexExecRequest,
7
+ CodexLocalClient,
8
+ RetryPolicy,
9
+ SandboxMode,
10
+ )
11
+
12
+
13
+ def on_event(event: CodexClientEvent) -> None:
14
+ print(
15
+ event.type,
16
+ event.operation,
17
+ event.attempt,
18
+ event.return_code,
19
+ event.turn_status,
20
+ )
21
+
22
+
23
+ def main() -> None:
24
+ client = CodexLocalClient(
25
+ retry_policy=RetryPolicy(
26
+ max_attempts=3,
27
+ initial_backoff_seconds=0.5,
28
+ backoff_multiplier=2.0,
29
+ max_backoff_seconds=4.0,
30
+ retry_on_exit_codes=None,
31
+ jitter_ratio=0.2,
32
+ max_total_retry_seconds=10.0,
33
+ retry_on_timeouts=True,
34
+ ),
35
+ event_hook=on_event,
36
+ )
37
+
38
+ request = CodexExecRequest(
39
+ prompt="TODO: replace with your prompt",
40
+ sandbox=SandboxMode.READ_ONLY,
41
+ json_output=True,
42
+ )
43
+
44
+ try:
45
+ result = client.run(request, timeout_seconds=60)
46
+ except CodexExecFailedError as exc:
47
+ print("failed return_code:", exc.result.return_code)
48
+ print("stderr:\n", exc.result.stderr)
49
+ return
50
+
51
+ print("success turn_status:", result.turn_status)
52
+ print("final_message:\n", result.final_message)
53
+
54
+
55
+ if __name__ == "__main__":
56
+ main()
@@ -0,0 +1,29 @@
1
+ """Template: start a thread and continue it in-process."""
2
+
3
+ from codex_local_sdk import CodexLocalClient, SandboxMode
4
+
5
+
6
+ def main() -> None:
7
+ client = CodexLocalClient()
8
+
9
+ session, first = client.start_thread(
10
+ prompt="TODO: initial prompt",
11
+ sandbox=SandboxMode.READ_ONLY,
12
+ timeout_seconds=120,
13
+ )
14
+
15
+ print("session_id:", session.session_id)
16
+ print("first turn status:", first.turn_status)
17
+
18
+ second = session.continue_prompt(
19
+ "TODO: follow-up prompt",
20
+ json_output=True,
21
+ timeout_seconds=120,
22
+ )
23
+
24
+ print("second turn status:", second.turn_status)
25
+ print("second final message:\n", second.final_message)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -0,0 +1,55 @@
1
+ # Guardrails and Failure Modes
2
+
3
+ Use this file when generated consumer code fails or behaves unexpectedly.
4
+
5
+ ## Handle Non-Zero Exits Explicitly
6
+ `CodexLocalClient` defaults to `raise_on_error=True`.
7
+
8
+ ```python
9
+ from codex_local_sdk import CodexExecFailedError
10
+
11
+ try:
12
+ result = client.run(request, timeout_seconds=90)
13
+ except CodexExecFailedError as exc:
14
+ print(exc.result.return_code)
15
+ print(exc.result.stderr)
16
+ ```
17
+
18
+ Set `raise_on_error=False` only when caller logic intentionally handles failed results directly.
19
+
20
+ ## Understand Timeout Behavior
21
+ - Sync command execution uses `subprocess.run(..., timeout=timeout_seconds)`.
22
+ - Timeout results are represented with return code `124`.
23
+ - Retries on timeout follow `RetryPolicy.retry_on_timeouts`.
24
+
25
+ ## Tune Retries Pragmatically
26
+ `RetryPolicy` applies to sync command execution (`run`, `resume`, `run_with_schema`, etc.):
27
+ - `max_attempts`
28
+ - `initial_backoff_seconds`
29
+ - `backoff_multiplier`
30
+ - `max_backoff_seconds`
31
+ - `retry_on_exit_codes` (`None` means any non-zero)
32
+ - `jitter_ratio`
33
+ - `max_total_retry_seconds`
34
+ - `retry_on_timeouts`
35
+
36
+ Live methods only retry startup failures; they do not auto-retry after a live handle is returned.
37
+
38
+ ## Keep Session Calls Valid
39
+ - Do not pass both `session_id` and `session_name`.
40
+ - For named persistence, configure `JsonFileSessionStore` at client construction.
41
+ - Use `last=False` when you want explicit session targeting instead of "last session" behavior.
42
+
43
+ ## Use Structured Output Deliberately
44
+ - Set `json_output=True` when code depends on `events`, `thread_id`, or `turn_status`.
45
+ - Use `run_with_schema` for machine-parseable outputs instead of fragile free-text parsing.
46
+
47
+ ## Observe Without Breaking Execution
48
+ `event_hook` failures are swallowed by the SDK. Keep hooks lightweight and side-effect-safe.
49
+
50
+ ## Fast Triage Checklist
51
+ 1. Confirm `codex` exists in `PATH`.
52
+ 2. Confirm auth is available (`CODEX_API_KEY` or active local Codex auth).
53
+ 3. Print `result.stderr` and `result.return_code`.
54
+ 4. For event-dependent logic, verify `json_output=True`.
55
+ 5. For resume flows, verify stored `session_name` and session store file path.
@@ -0,0 +1,72 @@
1
+ # SDK API Cheatsheet
2
+
3
+ Use this file to wire consumer code with correct classes and parameters.
4
+
5
+ ## Core Imports
6
+ ```python
7
+ from codex_local_sdk import (
8
+ AsyncCodexLiveRun,
9
+ CodexClientEvent,
10
+ CodexExecFailedError,
11
+ CodexExecRequest,
12
+ CodexLocalClient,
13
+ CodexThreadSession,
14
+ JsonFileSessionStore,
15
+ RetryPolicy,
16
+ SandboxMode,
17
+ )
18
+ ```
19
+
20
+ ## Request Model
21
+ `CodexExecRequest` fields:
22
+ - `prompt`
23
+ - `cwd`
24
+ - `json_output`
25
+ - `model`
26
+ - `profile`
27
+ - `sandbox` (`SandboxMode.READ_ONLY`, `WORKSPACE_WRITE`, `DANGER_FULL_ACCESS`)
28
+ - `full_auto`
29
+ - `ephemeral`
30
+ - `skip_git_repo_check`
31
+ - `output_last_message_path`
32
+ - `output_schema_path`
33
+ - `images` (tuple of paths)
34
+ - `extra_args` (tuple of additional CLI args)
35
+
36
+ ## Main Client Methods
37
+ - `run(request, api_key=None, timeout_seconds=None) -> CodexExecResult`
38
+ - `run_async(request, api_key=None, timeout_seconds=None) -> CodexExecResult`
39
+ - `run_live(request, api_key=None) -> CodexLiveRun`
40
+ - `run_live_async(request, api_key=None) -> AsyncCodexLiveRun`
41
+ - `run_prompt(prompt, **kwargs) -> CodexExecResult`
42
+ - `run_with_schema(prompt, schema, output_json_path=None, **kwargs) -> CodexExecResult`
43
+ - `start_thread(prompt, session_name=None, timeout_seconds=None, **request_overrides) -> (CodexThreadSession, CodexExecResult)`
44
+ - `resume(prompt, session_id=None, session_name=None, last=True, all_sessions=False, json_output=False, timeout_seconds=None) -> CodexExecResult`
45
+ - `resume_live(prompt, session_id=None, session_name=None, last=True, all_sessions=False) -> CodexLiveRun`
46
+ - `open_session(name) -> CodexThreadSession`
47
+
48
+ ## Thread Session Methods
49
+ - `continue_prompt(prompt, json_output=True, timeout_seconds=None)`
50
+ - `continue_prompt_async(prompt, json_output=True, timeout_seconds=None)`
51
+ - `continue_live(prompt)`
52
+ - `continue_live_async(prompt)`
53
+ - `is_last_turn_complete`
54
+
55
+ ## Result Fields
56
+ `CodexExecResult` includes:
57
+ - `ok`
58
+ - `return_code`
59
+ - `stdout`
60
+ - `stderr`
61
+ - `final_message`
62
+ - `thread_id`
63
+ - `turn_status`
64
+ - `usage`
65
+ - `events`
66
+ - `duration_seconds`
67
+ - `is_turn_completed`, `is_turn_failed`, `is_turn_terminal`
68
+
69
+ ## Exception Types
70
+ - `CodexNotInstalledError`: `codex` binary not available.
71
+ - `CodexExecFailedError`: non-zero return code when `raise_on_error=True`.
72
+ - `CodexError`: base exception.
@@ -0,0 +1,26 @@
1
+ # Task Routing
2
+
3
+ Map user intent to the right SDK entry point before writing code.
4
+
5
+ ## Decision Table
6
+ | User intent | Use these methods | Suggested template | Key setting |
7
+ | --- | --- | --- | --- |
8
+ | One-shot prompt and final text | `CodexLocalClient.run` or `run_prompt` | `template_sync_run.py` | `timeout_seconds` |
9
+ | Require structured events and turn metadata | `run` with `CodexExecRequest(json_output=True)` | `template_sync_run.py` | `json_output=True` |
10
+ | Stream live events as they happen | `run_live` | `template_live_stream.py` | Iterate `live.iter_events()` then `live.wait()` |
11
+ | Continue multi-turn flow in one process | `start_thread` + `CodexThreadSession.continue_prompt` | `template_thread_session.py` | `start_thread` enforces JSON output |
12
+ | Resume thread later by logical name | `JsonFileSessionStore` + `resume(session_name=..., last=False)` | `template_resume_named_session.py` | Reuse stable `session_name` |
13
+ | Enforce JSON schema in assistant output | `run_with_schema` | `template_schema_output.py` | Provide strict schema and output path |
14
+ | Use asyncio end-to-end | `run_async`, `run_live_async`, `continue_prompt_async` | `template_async_run.py` | `asyncio.run(main())` |
15
+ | Add observability + retry policy | `CodexLocalClient(event_hook=..., retry_policy=...)` | `template_telemetry_and_retry.py` | Tune `RetryPolicy` |
16
+
17
+ ## Session Strategy
18
+ - Use `session_id` for short-lived, in-memory flows.
19
+ - Use `session_name` with `JsonFileSessionStore` for cross-process continuation.
20
+ - Use `open_session(name)` when a session already exists and you want a `CodexThreadSession` handle.
21
+
22
+ ## Fallback Rules
23
+ - If user needs only final text, avoid live mode.
24
+ - If user needs incremental progress or event-level control, choose live mode.
25
+ - If user requests "continue previous work" and no ID is provided, use named sessions.
26
+ - If user needs machine-readable output, prefer schema flow over post-hoc parsing.
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env python3
2
+ """Run quick environment checks before using codex_local_sdk in consumer scripts."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import importlib
8
+ import os
9
+ from pathlib import Path
10
+ import shutil
11
+ import subprocess
12
+ import sys
13
+ from dataclasses import dataclass
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class CheckResult:
18
+ name: str
19
+ status: str
20
+ detail: str
21
+
22
+
23
+ def _discover_repo_root(start: Path) -> Path | None:
24
+ for candidate in (start, *start.parents):
25
+ if (candidate / "codex_local_sdk").is_dir():
26
+ return candidate
27
+ return None
28
+
29
+
30
+ def _prepare_import_path() -> str | None:
31
+ root = _discover_repo_root(Path(__file__).resolve().parent)
32
+ if root is None:
33
+ return None
34
+
35
+ root_str = str(root)
36
+ if root_str not in sys.path:
37
+ sys.path.insert(0, root_str)
38
+ return root_str
39
+
40
+
41
+ def check_python() -> CheckResult:
42
+ version = sys.version_info
43
+ detail = f"{version.major}.{version.minor}.{version.micro}"
44
+ if version >= (3, 10):
45
+ return CheckResult("python", "PASS", detail)
46
+ return CheckResult("python", "FAIL", f"{detail} (require >= 3.10)")
47
+
48
+
49
+ def check_sdk_import() -> CheckResult:
50
+ repo_root = _prepare_import_path()
51
+ try:
52
+ module = importlib.import_module("codex_local_sdk")
53
+ except Exception as exc: # pragma: no cover - defensive
54
+ return CheckResult("sdk_import", "FAIL", str(exc))
55
+
56
+ exported = getattr(module, "__all__", ())
57
+ detail = f"import ok; exports={len(tuple(exported))}"
58
+ if repo_root:
59
+ detail = f"{detail}; repo_root={repo_root}"
60
+ return CheckResult("sdk_import", "PASS", detail)
61
+
62
+
63
+ def check_codex_cli() -> CheckResult:
64
+ codex_path = shutil.which("codex")
65
+ if not codex_path:
66
+ return CheckResult("codex_cli", "WARN", "`codex` not found in PATH")
67
+
68
+ try:
69
+ completed = subprocess.run(
70
+ [codex_path, "--version"],
71
+ check=False,
72
+ capture_output=True,
73
+ text=True,
74
+ timeout=5,
75
+ )
76
+ except Exception as exc: # pragma: no cover - defensive
77
+ return CheckResult("codex_cli", "WARN", f"found at {codex_path}; version probe failed: {exc}")
78
+
79
+ version_text = (completed.stdout or completed.stderr or "").strip() or "unknown version"
80
+ return CheckResult("codex_cli", "PASS", f"{codex_path}; {version_text}")
81
+
82
+
83
+ def check_auth_hint() -> CheckResult:
84
+ if os.environ.get("CODEX_API_KEY"):
85
+ return CheckResult("auth", "PASS", "CODEX_API_KEY is set")
86
+ return CheckResult("auth", "WARN", "CODEX_API_KEY not set (ok if local Codex auth session is active)")
87
+
88
+
89
+ def render(results: list[CheckResult]) -> int:
90
+ print("SDK consumer preflight")
91
+ print("======================")
92
+ for result in results:
93
+ print(f"[{result.status}] {result.name}: {result.detail}")
94
+
95
+ failed = any(r.status == "FAIL" for r in results)
96
+ warned = any(r.status == "WARN" for r in results)
97
+
98
+ if failed:
99
+ print("\nResult: not ready (hard failures detected)")
100
+ return 2
101
+ if warned:
102
+ print("\nResult: usable with warnings")
103
+ return 1
104
+
105
+ print("\nResult: ready")
106
+ return 0
107
+
108
+
109
+ def main() -> int:
110
+ parser = argparse.ArgumentParser(description="Check local readiness for codex_local_sdk consumer scripts.")
111
+ parser.add_argument(
112
+ "--strict",
113
+ action="store_true",
114
+ help="Exit non-zero when warnings are present.",
115
+ )
116
+ args = parser.parse_args()
117
+
118
+ results = [
119
+ check_python(),
120
+ check_sdk_import(),
121
+ check_codex_cli(),
122
+ check_auth_hint(),
123
+ ]
124
+
125
+ code = render(results)
126
+ if code == 1 and not args.strict:
127
+ return 0
128
+ return code
129
+
130
+
131
+ if __name__ == "__main__":
132
+ raise SystemExit(main())
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ """Copy a codex_local_sdk usage template from assets/ into a target path."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+
9
+
10
+ def template_map(assets_dir: Path) -> dict[str, Path]:
11
+ return {
12
+ "sync": assets_dir / "template_sync_run.py",
13
+ "live": assets_dir / "template_live_stream.py",
14
+ "thread": assets_dir / "template_thread_session.py",
15
+ "resume": assets_dir / "template_resume_named_session.py",
16
+ "async": assets_dir / "template_async_run.py",
17
+ "schema": assets_dir / "template_schema_output.py",
18
+ "telemetry": assets_dir / "template_telemetry_and_retry.py",
19
+ }
20
+
21
+
22
+ def list_templates(templates: dict[str, Path]) -> None:
23
+ print("Available templates:")
24
+ for key in sorted(templates):
25
+ print(f"- {key}: {templates[key].name}")
26
+
27
+
28
+ def copy_template(source: Path, output: Path, force: bool) -> None:
29
+ if not source.exists():
30
+ raise FileNotFoundError(f"Template not found: {source}")
31
+
32
+ if output.exists() and not force:
33
+ raise FileExistsError(f"Output already exists: {output}. Pass --force to overwrite.")
34
+
35
+ output.parent.mkdir(parents=True, exist_ok=True)
36
+ output.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")
37
+
38
+
39
+ def main() -> int:
40
+ script_dir = Path(__file__).resolve().parent
41
+ assets_dir = script_dir.parent / "assets"
42
+ templates = template_map(assets_dir)
43
+
44
+ parser = argparse.ArgumentParser(description="Create a new consumer script from a usage template.")
45
+ parser.add_argument("--list", action="store_true", help="List template keys and exit.")
46
+ parser.add_argument("--template", choices=sorted(templates), help="Template key to copy.")
47
+ parser.add_argument("--output", help="Destination file path.")
48
+ parser.add_argument("--force", action="store_true", help="Overwrite output if it already exists.")
49
+ args = parser.parse_args()
50
+
51
+ if args.list:
52
+ list_templates(templates)
53
+ return 0
54
+
55
+ if not args.template or not args.output:
56
+ parser.error("--template and --output are required unless --list is used.")
57
+
58
+ source = templates[args.template]
59
+ destination = Path(args.output).expanduser().resolve()
60
+ copy_template(source, destination, force=args.force)
61
+ print(f"Wrote {destination} from {source.name}")
62
+ return 0
63
+
64
+
65
+ if __name__ == "__main__":
66
+ raise SystemExit(main())