coding-agent-cli 0.1.0__tar.gz → 0.2.0__tar.gz

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 (261) hide show
  1. coding_agent_cli-0.2.0/.release-please-manifest.json +3 -0
  2. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/AGENTS.md +9 -6
  3. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/CHANGELOG.md +16 -0
  4. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/CONTRIBUTING.md +8 -0
  5. coding_agent_cli-0.2.0/PKG-INFO +290 -0
  6. coding_agent_cli-0.2.0/README.md +267 -0
  7. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/.handover.md +69 -0
  8. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/DESIGN.md +421 -0
  9. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/PLAN.md +333 -0
  10. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-01/journal.md +166 -0
  11. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-01/notes.md +164 -0
  12. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-01/prompt.md +70 -0
  13. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-02/journal.md +130 -0
  14. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-02/notes.md +118 -0
  15. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-02/prompt.md +72 -0
  16. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-03/journal.md +121 -0
  17. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-03/notes.md +153 -0
  18. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-03/prompt.md +69 -0
  19. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-04/journal.md +96 -0
  20. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-04/notes.md +148 -0
  21. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-04/prompt.md +73 -0
  22. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-05/journal.md +76 -0
  23. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-05/notes.md +76 -0
  24. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-05/prompt.md +59 -0
  25. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-06/journal.md +88 -0
  26. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-06/notes.md +110 -0
  27. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-06/prompt.md +59 -0
  28. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-07/journal.md +86 -0
  29. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-07/notes.md +78 -0
  30. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-07/prompt.md +59 -0
  31. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-08/journal.md +104 -0
  32. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-08/notes.md +112 -0
  33. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-08/prompt.md +59 -0
  34. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-09/journal.md +50 -0
  35. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-09/notes.md +33 -0
  36. coding_agent_cli-0.2.0/docs/plans/coding-agent-cli-v2/phase-09/prompt.md +37 -0
  37. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/pyproject.toml +2 -2
  38. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/release-please-config.json +1 -0
  39. coding_agent_cli-0.2.0/scripts/record_fixtures.py +847 -0
  40. coding_agent_cli-0.2.0/scripts/scrub_fixtures.py +264 -0
  41. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/__init__.py +21 -7
  42. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/_args.py +28 -8
  43. coding_agent_cli-0.2.0/src/coding_agent_cli/_classify.py +261 -0
  44. coding_agent_cli-0.2.0/src/coding_agent_cli/_drivers/_common.py +21 -0
  45. coding_agent_cli-0.2.0/src/coding_agent_cli/_drivers/agy.py +316 -0
  46. coding_agent_cli-0.2.0/src/coding_agent_cli/_drivers/claude.py +449 -0
  47. coding_agent_cli-0.2.0/src/coding_agent_cli/_drivers/codex.py +412 -0
  48. coding_agent_cli-0.2.0/src/coding_agent_cli/_drivers/pi.py +230 -0
  49. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/_platform.py +71 -1
  50. coding_agent_cli-0.2.0/src/coding_agent_cli/_transport.py +520 -0
  51. coding_agent_cli-0.2.0/src/coding_agent_cli/api.py +190 -0
  52. coding_agent_cli-0.2.0/src/coding_agent_cli/driver.py +308 -0
  53. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/events.py +50 -9
  54. coding_agent_cli-0.2.0/src/coding_agent_cli/registry.py +43 -0
  55. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/types.py +45 -5
  56. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/both_flood.py +12 -0
  57. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/line_flood.py +4 -0
  58. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/oversized_then_valid.py +6 -0
  59. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/print_then_exit.py +3 -0
  60. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/silent_then_output.py +7 -0
  61. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/slow_callback.py +7 -0
  62. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/slow_trickle.py +11 -0
  63. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/stubborn_descendant.py +15 -0
  64. coding_agent_cli-0.2.0/tests/fixtures/fake_cli/two_messages.py +4 -0
  65. coding_agent_cli-0.2.0/tests/fixtures/recorded/FINDINGS.md +110 -0
  66. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/cancel.meta.json +24 -0
  67. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/cancel.stderr.txt +1 -0
  68. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/cancel.stdout.jsonl +4 -0
  69. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/edit.meta.json +25 -0
  70. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/edit.stderr.txt +0 -0
  71. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/edit.stdout.jsonl +16 -0
  72. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/invalid_model.meta.json +24 -0
  73. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/invalid_model.stderr.txt +20 -0
  74. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/invalid_model.stdout.jsonl +1 -0
  75. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/plain.meta.json +24 -0
  76. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/plain.stderr.txt +0 -0
  77. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/plain.stdout.jsonl +5 -0
  78. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/reasoning.meta.json +26 -0
  79. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/reasoning.stderr.txt +0 -0
  80. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/reasoning.stdout.jsonl +5 -0
  81. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/resume.meta.json +25 -0
  82. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/resume.stderr.txt +0 -0
  83. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/resume.stdout.jsonl +6 -0
  84. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/shell.meta.json +25 -0
  85. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/shell.stderr.txt +0 -0
  86. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/shell.stdout.jsonl +15 -0
  87. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/stdin_stream_json.meta.json +25 -0
  88. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/stdin_stream_json.stderr.txt +1 -0
  89. coding_agent_cli-0.2.0/tests/fixtures/recorded/agy/1.5.9/stdin_stream_json.stdout.jsonl +2 -0
  90. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/auth_failure.meta.json +27 -0
  91. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/auth_failure.stderr.txt +0 -0
  92. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/auth_failure.stdout.jsonl +4 -0
  93. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/budget.meta.json +27 -0
  94. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/budget.stderr.txt +0 -0
  95. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/budget.stdout.jsonl +46 -0
  96. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/cancel.meta.json +27 -0
  97. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/cancel.stderr.txt +0 -0
  98. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/cancel.stdout.jsonl +22 -0
  99. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/edit.meta.json +31 -0
  100. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/edit.stderr.txt +0 -0
  101. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/edit.stdout.jsonl +78 -0
  102. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/invalid_model.meta.json +27 -0
  103. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/invalid_model.stderr.txt +1 -0
  104. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/invalid_model.stdout.jsonl +4 -0
  105. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/max_turns.meta.json +33 -0
  106. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/max_turns.stderr.txt +0 -0
  107. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/max_turns.stdout.jsonl +38 -0
  108. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/no_verbose.meta.json +25 -0
  109. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/no_verbose.stderr.txt +1 -0
  110. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/no_verbose.stdout.jsonl +0 -0
  111. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/plain.meta.json +27 -0
  112. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/plain.stderr.txt +0 -0
  113. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/plain.stdout.jsonl +11 -0
  114. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/reasoning.meta.json +29 -0
  115. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/reasoning.stderr.txt +0 -0
  116. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/reasoning.stdout.jsonl +11 -0
  117. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/resume.meta.json +29 -0
  118. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/resume.stderr.txt +0 -0
  119. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/resume.stdout.jsonl +11 -0
  120. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/shell.meta.json +31 -0
  121. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/shell.stderr.txt +0 -0
  122. coding_agent_cli-0.2.0/tests/fixtures/recorded/claude/2.1.296/shell.stdout.jsonl +34 -0
  123. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/auth_failure.meta.json +17 -0
  124. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/auth_failure.stderr.txt +8 -0
  125. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/auth_failure.stdout.jsonl +14 -0
  126. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/cancel.meta.json +19 -0
  127. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/cancel.stderr.txt +0 -0
  128. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/cancel.stdout.jsonl +5 -0
  129. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/edit.meta.json +19 -0
  130. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/edit.stderr.txt +0 -0
  131. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/edit.stdout.jsonl +11 -0
  132. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/invalid_model.meta.json +19 -0
  133. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/invalid_model.stderr.txt +0 -0
  134. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/invalid_model.stdout.jsonl +7 -0
  135. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/plain.meta.json +17 -0
  136. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/plain.stderr.txt +0 -0
  137. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/plain.stdout.jsonl +6 -0
  138. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/reasoning.meta.json +19 -0
  139. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/reasoning.stderr.txt +0 -0
  140. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/reasoning.stdout.jsonl +6 -0
  141. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/resume.meta.json +19 -0
  142. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/resume.stderr.txt +0 -0
  143. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/resume.stdout.jsonl +6 -0
  144. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/shell.meta.json +19 -0
  145. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/shell.stderr.txt +0 -0
  146. coding_agent_cli-0.2.0/tests/fixtures/recorded/codex/0.169.0/shell.stdout.jsonl +9 -0
  147. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/auth_failure.meta.json +27 -0
  148. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/auth_failure.stderr.txt +5 -0
  149. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/auth_failure.stdout.jsonl +1 -0
  150. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/cancel.meta.json +27 -0
  151. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/cancel.stderr.txt +0 -0
  152. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/cancel.stdout.jsonl +30 -0
  153. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/edit.meta.json +27 -0
  154. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/edit.stderr.txt +0 -0
  155. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/edit.stdout.jsonl +92 -0
  156. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/invalid_model.meta.json +27 -0
  157. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/invalid_model.stderr.txt +1 -0
  158. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/invalid_model.stdout.jsonl +0 -0
  159. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/no_p.meta.json +26 -0
  160. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/no_p.stderr.txt +0 -0
  161. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/no_p.stdout.jsonl +16 -0
  162. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/plain.meta.json +27 -0
  163. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/plain.stderr.txt +0 -0
  164. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/plain.stdout.jsonl +16 -0
  165. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/reasoning.meta.json +29 -0
  166. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/reasoning.stderr.txt +0 -0
  167. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/reasoning.stdout.jsonl +15 -0
  168. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/resume.meta.json +29 -0
  169. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/resume.stderr.txt +0 -0
  170. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/resume.stdout.jsonl +14 -0
  171. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/shell.meta.json +27 -0
  172. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/shell.stderr.txt +0 -0
  173. coding_agent_cli-0.2.0/tests/fixtures/recorded/pi/1.5.2/shell.stdout.jsonl +40 -0
  174. coding_agent_cli-0.2.0/tests/procs.py +53 -0
  175. coding_agent_cli-0.2.0/tests/replay.py +110 -0
  176. coding_agent_cli-0.2.0/tests/test_agy_driver.py +507 -0
  177. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/test_api_contract.py +57 -3
  178. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/test_args.py +13 -2
  179. coding_agent_cli-0.2.0/tests/test_classify.py +330 -0
  180. coding_agent_cli-0.2.0/tests/test_claude_driver.py +571 -0
  181. coding_agent_cli-0.2.0/tests/test_codex_driver.py +598 -0
  182. coding_agent_cli-0.2.0/tests/test_driver_base.py +312 -0
  183. coding_agent_cli-0.2.0/tests/test_events.py +87 -0
  184. coding_agent_cli-0.2.0/tests/test_fixture_privacy.py +313 -0
  185. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/test_live.py +6 -7
  186. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/test_packaging.py +5 -1
  187. coding_agent_cli-0.2.0/tests/test_pi_driver.py +435 -0
  188. coding_agent_cli-0.2.0/tests/test_readme.py +34 -0
  189. coding_agent_cli-0.2.0/tests/test_record_fixtures.py +300 -0
  190. coding_agent_cli-0.2.0/tests/test_registry.py +80 -0
  191. coding_agent_cli-0.2.0/tests/test_replay.py +88 -0
  192. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/test_stream.py +34 -2
  193. coding_agent_cli-0.2.0/tests/test_transport.py +772 -0
  194. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/test_types.py +86 -7
  195. coding_agent_cli-0.2.0/tests/toy_driver.py +47 -0
  196. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/uv.lock +2 -2
  197. coding_agent_cli-0.1.0/.release-please-manifest.json +0 -3
  198. coding_agent_cli-0.1.0/PKG-INFO +0 -217
  199. coding_agent_cli-0.1.0/README.md +0 -194
  200. coding_agent_cli-0.1.0/src/coding_agent_cli/_classify.py +0 -170
  201. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/_common.py +0 -330
  202. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/_usage.py +0 -77
  203. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/agy.py +0 -340
  204. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/claude.py +0 -304
  205. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/codex.py +0 -333
  206. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/codex_rollout.py +0 -439
  207. coding_agent_cli-0.1.0/src/coding_agent_cli/_drivers/pi.py +0 -333
  208. coding_agent_cli-0.1.0/src/coding_agent_cli/_process.py +0 -338
  209. coding_agent_cli-0.1.0/src/coding_agent_cli/api.py +0 -190
  210. coding_agent_cli-0.1.0/src/coding_agent_cli/registry.py +0 -67
  211. coding_agent_cli-0.1.0/tests/cli_stub.py +0 -144
  212. coding_agent_cli-0.1.0/tests/fixtures/usage/agy.jsonl +0 -6
  213. coding_agent_cli-0.1.0/tests/fixtures/usage/claude.jsonl +0 -12
  214. coding_agent_cli-0.1.0/tests/fixtures/usage/codex.jsonl +0 -7
  215. coding_agent_cli-0.1.0/tests/fixtures/usage/pi.jsonl +0 -55
  216. coding_agent_cli-0.1.0/tests/test_agy_driver.py +0 -723
  217. coding_agent_cli-0.1.0/tests/test_classify.py +0 -88
  218. coding_agent_cli-0.1.0/tests/test_claude_driver.py +0 -735
  219. coding_agent_cli-0.1.0/tests/test_codex_driver.py +0 -692
  220. coding_agent_cli-0.1.0/tests/test_codex_rollout.py +0 -610
  221. coding_agent_cli-0.1.0/tests/test_events.py +0 -68
  222. coding_agent_cli-0.1.0/tests/test_pi_driver.py +0 -746
  223. coding_agent_cli-0.1.0/tests/test_process.py +0 -394
  224. coding_agent_cli-0.1.0/tests/test_registry.py +0 -55
  225. coding_agent_cli-0.1.0/tests/test_usage.py +0 -87
  226. coding_agent_cli-0.1.0/tests/toy_driver.py +0 -68
  227. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.editorconfig +0 -0
  228. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  229. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  230. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  231. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  232. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/dependabot.yml +0 -0
  233. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/workflows/ci.yml +0 -0
  234. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/workflows/pr-title.yml +0 -0
  235. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.github/workflows/release.yml +0 -0
  236. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/.gitignore +0 -0
  237. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/CLAUDE.md +0 -0
  238. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/CODE_OF_CONDUCT.md +0 -0
  239. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/LICENSE +0 -0
  240. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/SECURITY.md +0 -0
  241. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/docs/plans/coding-agent-cli/.handover.md +0 -0
  242. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/docs/plans/coding-agent-cli/DESIGN.md +0 -0
  243. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/docs/plans/coding-agent-cli/PLAN.md +0 -0
  244. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/docs/plans/coding-agent-cli/journal-phase4.md +0 -0
  245. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/docs/plans/coding-agent-cli/journal-phase5.md +0 -0
  246. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/_drivers/__init__.py +0 -0
  247. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/src/coding_agent_cli/py.typed +0 -0
  248. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/__init__.py +0 -0
  249. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/conftest.py +0 -0
  250. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/echo_stdin.py +0 -0
  251. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/emit_both.py +0 -0
  252. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/exit_now.py +0 -0
  253. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/exit_status.py +0 -0
  254. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/fake_cli.py +0 -0
  255. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/grandchild.py +0 -0
  256. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/long_line.py +0 -0
  257. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/many_lines.py +0 -0
  258. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/non_utf8.py +0 -0
  259. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/sleeper.py +0 -0
  260. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/fixtures/fake_cli/spawn_and_sleep.py +0 -0
  261. {coding_agent_cli-0.1.0 → coding_agent_cli-0.2.0}/tests/launchers.py +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.0"
3
+ }
@@ -11,13 +11,16 @@ Guidance for AI coding agents working in this repository. Humans should read [CO
11
11
  | Path | Contents |
12
12
  |---|---|
13
13
  | `src/coding_agent_cli/api.py` | Public entry points: `run`, `stream`, `run_sync`, `check` |
14
- | `src/coding_agent_cli/types.py`, `events.py` | Public frozen dataclasses |
15
- | `src/coding_agent_cli/registry.py` | `Driver` protocol and `DriverRegistry` |
16
- | `src/coding_agent_cli/_process.py`, `_platform.py` | Subprocess transport and process-tree kill |
14
+ | `src/coding_agent_cli/types.py`, `events.py` | Public frozen dataclasses and typed events |
15
+ | `src/coding_agent_cli/driver.py` | Public `CliDriver`, `Launch`, `Parser`, and driver helpers |
16
+ | `src/coding_agent_cli/registry.py` | Built-in registration and `DriverRegistry` |
17
+ | `src/coding_agent_cli/_transport.py`, `_platform.py` | Subprocess transport and process-tree cleanup |
17
18
  | `src/coding_agent_cli/_args.py`, `_classify.py` | Argument validation and outcome classification |
18
- | `src/coding_agent_cli/_drivers/` | Built-in drivers and shared `CliDriver` helpers |
19
- | `tests/fixtures/fake_cli/` | Fake CLIs used by all unit tests |
20
- | `docs/plans/coding-agent-cli/` | Design, plan, and phase journals |
19
+ | `src/coding_agent_cli/_drivers/` | Built-in drivers and shared helpers |
20
+ | `tests/fixtures/fake_cli/`, `tests/fixtures/recorded/` | Fake CLIs and sanitized recorded fixtures |
21
+ | `tests/replay.py` | Recorded fixture replay helpers |
22
+ | `scripts/record_fixtures.py` | Developer fixture recording script, not shipped in the wheel |
23
+ | `docs/plans/coding-agent-cli-v2/` | Design, plan, and phase journals |
21
24
 
22
25
  ## Commands
23
26
 
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0](https://github.com/sitien173/coding-agent-cli/compare/v0.1.0...v0.2.0) (2026-11-14)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * rebuild on public CliDriver API with recorded CLI fixtures
9
+
10
+ ### Features
11
+
12
+ * rebuild on public CliDriver API with recorded CLI fixtures ([3a5de96](https://github.com/sitien173/coding-agent-cli/commit/3a5de960c13d4da99791d02f3ef28cfc61671713))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **scripts:** narrow recorder process-group kill so pyright passes on Windows ([b1dd2a0](https://github.com/sitien173/coding-agent-cli/commit/b1dd2a00d098220c316945091fc568f7dfb63da3))
18
+
3
19
  ## 0.1.0
4
20
 
5
21
  First release. Extracted from the CLI-driver layer of Refract.
@@ -59,8 +59,16 @@ uv run pytest -m live -rP
59
59
 
60
60
  - Every behavior change comes with a test. Write the failing test first when you can.
61
61
  - Tests use the fake CLIs in `tests/fixtures/fake_cli`. Unit tests never call a real coding-agent CLI.
62
+ - Recorded sanitized CLI fixtures live in `tests/fixtures/recorded/`; `tests/replay.py` provides replay helpers. `scripts/record_fixtures.py` records new fixtures and is a developer script, not part of the wheel.
62
63
  - A new driver needs argv tests proving it adds no permission flag for any option combination.
63
64
 
65
+ ### Module map
66
+
67
+ - `src/coding_agent_cli/driver.py` defines the public `CliDriver` extension API, `Launch`, and `Parser`.
68
+ - `src/coding_agent_cli/_transport.py` supervises subprocesses; `_classify.py` maps run facts to outcomes.
69
+ - `src/coding_agent_cli/_drivers/` contains the built-in CLI drivers and shared helpers.
70
+ - `src/coding_agent_cli/registry.py` registers built-in drivers and manages per-call registries.
71
+
64
72
  ### Adding a driver
65
73
 
66
74
  1. Subclass `CliDriver` in `src/coding_agent_cli/_drivers/`. Follow `claude.py` or `pi.py`.
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.5
2
+ Name: coding-agent-cli
3
+ Version: 0.2.0
4
+ Summary: Run coding-agent CLIs from Python, stream typed events, and get a classified result.
5
+ Project-URL: Homepage, https://github.com/sitien173/coding-agent-cli
6
+ Project-URL: Issues, https://github.com/sitien173/coding-agent-cli/issues
7
+ Project-URL: Changelog, https://github.com/sitien173/coding-agent-cli/blob/main/CHANGELOG.md
8
+ Author: sitien173
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: asyncio,claude,cli,codex,coding-agent,subprocess
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: AsyncIO
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+
24
+ # coding-agent-cli
25
+
26
+ [![CI](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml)
27
+ [![PyPI](https://img.shields.io/pypi/v/coding-agent-cli)](https://pypi.org/project/coding-agent-cli/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/coding-agent-cli)](https://pypi.org/project/coding-agent-cli/)
29
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
30
+
31
+ Run coding-agent CLIs from Python, stream typed events, and get a classified result.
32
+
33
+ Built-in drivers: `claude`, `codex`, `pi`, and `agy`. Python 3.12 or later on Linux, macOS, and Windows. No runtime dependencies. The CLIs themselves are not bundled; install and authenticate each CLI you use.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install coding-agent-cli
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ import asyncio
45
+ from pathlib import Path
46
+
47
+ from coding_agent_cli import Request, run
48
+
49
+
50
+ async def main() -> None:
51
+ result = await run(Request(driver="claude", prompt="Summarize README.md", cwd=Path.cwd()))
52
+ print(result.outcome, result.text)
53
+
54
+
55
+ asyncio.run(main())
56
+ ```
57
+
58
+ ## API
59
+
60
+ The public entry points have these signatures:
61
+
62
+ ```python
63
+ async def run(
64
+ req: Request,
65
+ *,
66
+ on_event: Callable[[Event], Awaitable[None] | None] | None = None,
67
+ cancel: asyncio.Event | None = None,
68
+ on_spawn: Callable[[int], Awaitable[None] | None] | None = None,
69
+ registry: DriverRegistry | None = None,
70
+ ) -> Result: ...
71
+
72
+
73
+ def stream(
74
+ req: Request,
75
+ *,
76
+ cancel: asyncio.Event | None = None,
77
+ on_spawn: Callable[[int], Awaitable[None] | None] | None = None,
78
+ registry: DriverRegistry | None = None,
79
+ ) -> EventStream: ...
80
+
81
+
82
+ def run_sync(
83
+ req: Request,
84
+ *,
85
+ on_event: Callable[[Event], None] | None = None,
86
+ cancel: threading.Event | None = None,
87
+ registry: DriverRegistry | None = None,
88
+ ) -> Result: ...
89
+
90
+
91
+ async def check(
92
+ driver: str,
93
+ *,
94
+ executable: str | None = None,
95
+ env: Mapping[str, str] | None = None,
96
+ refresh: bool = False,
97
+ registry: DriverRegistry | None = None,
98
+ ) -> Capabilities: ...
99
+ ```
100
+
101
+ `run` and `stream` are asynchronous. `run` accepts a sync or async event callback. `stream` is an async iterator; use it as an async context manager so leaving the block closes the run. Callback exceptions propagate after process cleanup. Unknown driver names raise `KeyError`.
102
+
103
+ ```python
104
+ import asyncio
105
+ from pathlib import Path
106
+
107
+ from coding_agent_cli import MessageCompleted, Request, RunCompleted, TextDelta, stream
108
+
109
+
110
+ async def main() -> None:
111
+ req = Request(driver="codex", prompt="List the tests", cwd=Path.cwd())
112
+ async with stream(req) as events:
113
+ async for event in events:
114
+ if isinstance(event, TextDelta):
115
+ print(event.text, end="")
116
+ elif isinstance(event, MessageCompleted):
117
+ print()
118
+ elif isinstance(event, RunCompleted):
119
+ print(event.result.outcome)
120
+
121
+
122
+ asyncio.run(main())
123
+ ```
124
+
125
+ `run_sync` is for synchronous applications and raises `RuntimeError` inside an active event loop. Its `threading.Event` cancellation signal can be set from another thread:
126
+
127
+ ```python
128
+ import threading
129
+ from pathlib import Path
130
+
131
+ from coding_agent_cli import Request, run_sync
132
+
133
+ cancel = threading.Event()
134
+ request = Request(driver="pi", prompt="Inspect this project", cwd=Path.cwd())
135
+ # Pass cancel to run_sync and call cancel.set() from another thread to stop the run.
136
+ result = run_sync(request, cancel=cancel)
137
+ ```
138
+
139
+ `check(driver, ...)` probes `--version` and `--help` and returns `Capabilities(available, executable, version, structured_streaming)`. Successful probe results are cached by executable path and modification time. `run` does not probe first.
140
+
141
+ ## Request and Result
142
+
143
+ `Request` is frozen and keyword-only:
144
+
145
+ | Field | Type | Meaning |
146
+ |---|---|---|
147
+ | `driver` | `str` | Registered driver name: `claude`, `codex`, `pi`, or `agy`. |
148
+ | `prompt` | `str` | Prompt text. |
149
+ | `cwd` | `Path` | Working directory for the CLI. |
150
+ | `session_id` | `str \| None` | Resume a CLI session when supported. |
151
+ | `model` | `str \| None` | Portable model option when supported. |
152
+ | `reasoning` | `str \| None` | Portable reasoning or effort option when supported. |
153
+ | `system_prompt` | `str \| None` | Portable system prompt when supported. |
154
+ | `args` | `tuple[str, ...]` | Additional CLI arguments passed through unchanged. Each driver decides their position; some drivers append the prompt or session arguments after them. |
155
+ | `env` | `Mapping[str, str]` | Environment overlay on the inherited environment. |
156
+ | `timeout_s` | `float \| None` | Positive finite wall-clock limit, or `None` for no limit. |
157
+ | `idle_timeout_s` | `float \| None` | Positive finite idle limit, reset by process output, or `None` for no limit. |
158
+ | `raw_events` | `bool` | Emit parsed JSON stdout lines as `RawEvent` events. |
159
+ | `executable` | `str \| None` | Executable path or name overriding the driver's default. |
160
+
161
+ `Result` is frozen and keyword-only. Its fields are `outcome: Outcome`, `text: str`, `session_id: str | None`, `model: str | None`, `usage: Usage`, `cost_usd: float | None`, `duration_s: float`, `num_turns: int | None`, `stop_reason: str | None`, `returncode: int | None`, `error: Diagnostic | None`, and `warnings: tuple[Diagnostic, ...]`. `text` is the parser's reported final text when the CLI provides one, otherwise the accumulated text of the last assistant message. It is never the concatenation of every assistant turn. Unknown reported values remain `None`. Codex does not report a model, so its `Result.model` is `None`.
162
+
163
+ ## Events
164
+
165
+ Each event has an increasing `sequence` and optional CLI-native `entity_id`; the `entity_id` is `None` when the CLI provides no native identifier. Events have no `schema_version` field. The module constant `SCHEMA_VERSION` is `2`. A run that returns a `Result` emits one final `RunCompleted` after cleanup. If cleanup misses its deadline, the result carries a `cleanup_incomplete` warning and termination is not confirmed. If `on_event` or `on_spawn` raises, or the calling task is cancelled, the library cleans up and re-raises without emitting `RunCompleted`.
166
+
167
+ | Event | `kind` | Additional fields |
168
+ |---|---|---|
169
+ | `SessionIdentified` | `session.identified` | `session_id` |
170
+ | `MessageStarted` | `assistant.message.started` | none |
171
+ | `TextDelta` | `assistant.text.delta` | `text` |
172
+ | `ReasoningDelta` | `assistant.reasoning.delta` | `text` |
173
+ | `MessageCompleted` | `assistant.message.completed` | none |
174
+ | `ToolStarted` | `tool.started` | `tool`, `activity` (`command` or `tool_call`), `input` |
175
+ | `ToolCompleted` | `tool.completed` | `tool`, `status` (`completed`, `error`, or `unknown`), `output` |
176
+ | `FileChanged` | `file.changed` | `path`, `change` (`added`, `modified`, `deleted`, or `unknown`), `evidence` (`reported` or `inferred`) |
177
+ | `Notice` | `notice` | `code`, `message` |
178
+ | `UsageUpdated` | `usage.updated` | `usage` |
179
+ | `RawEvent` | `raw` | `stream`, `data` |
180
+ | `RunCompleted` | `run.completed` | `result` |
181
+
182
+ ## Outcomes and errors
183
+
184
+ | `Outcome` | Meaning |
185
+ |---|---|
186
+ | `success` | The CLI completed successfully. |
187
+ | `retryable` | The run may succeed later, for example after a timeout, service error, nonzero exit, or missing output. |
188
+ | `limit_reached` | A CLI-reported turn or budget limit stopped the run. |
189
+ | `agent_fatal` | The CLI cannot run here, such as when it is missing or authentication fails. |
190
+ | `request_fatal` | The request is invalid or unsupported. |
191
+ | `cancelled` | The caller cancelled the run. |
192
+
193
+ `ErrorCode` values are `bad_cwd`, `invalid_args`, `unsupported_option`, `invalid_prompt`, `missing_cli`, `spawn_failed`, `auth`, `invalid_model`, `context_overflow`, `service_error`, `max_turns`, `budget`, `cancelled`, `timeout`, `idle_timeout`, `stdin_failed`, `execution_error`, `parser_error`, `no_output`, `no_session_id`, `line_too_long`, and `cleanup_incomplete`. `Result.error` is a `Diagnostic(code, message)` when the run has a primary error; `Result.warnings` contains non-fatal diagnostics.
194
+
195
+ `retryable` means possibly transient, not safe to replay. The agent may already have edited files, run commands, or spent tokens. Inspect the working tree before sending the same prompt again.
196
+
197
+ ## CLI arguments and permissions
198
+
199
+ Drivers do not add permission, approval, or sandbox-bypass flags. Callers pass any CLI-specific permission flags through `Request.args`:
200
+
201
+ | CLI | Example flag |
202
+ |---|---|
203
+ | Claude Code | `--permission-mode bypassPermissions` |
204
+ | Codex | `--dangerously-bypass-approvals-and-sandbox` |
205
+ | Pi | No automatic permission flag; `--approve` trusts project-local files and is not a tool-permission bypass. |
206
+ | Agy | `--dangerously-skip-permissions` |
207
+
208
+ Only use unattended permission flags in an environment you are prepared to lose. Portable `model`, `reasoning`, and `system_prompt` options are supported selectively; unsupported options and conflicts with `args` are classified as request errors.
209
+
210
+ ## Custom drivers
211
+
212
+ Subclass `CliDriver` and implement `launch` and `parser`. `Launch` describes argv, optional stdin, and files to read back after the run. `Parser` converts JSON or text lines into events and final `Facts`:
213
+
214
+ ```python
215
+ from collections.abc import Iterable, Mapping
216
+ from pathlib import Path
217
+ from typing import Any, Literal
218
+
219
+ from coding_agent_cli import CliDriver, DriverRegistry, Event, Facts, Launch, Parser, Request
220
+
221
+
222
+ class ExampleParser:
223
+ def feed_json(self, obj: dict[str, Any]) -> Iterable[Event]:
224
+ return ()
225
+
226
+ def feed_text(self, line: str, *, stream: Literal["stdout", "stderr"]) -> Iterable[Event]:
227
+ return ()
228
+
229
+ def finish(self, read_back: Mapping[Path, bytes]) -> Facts:
230
+ return Facts()
231
+
232
+
233
+ class ExampleDriver(CliDriver):
234
+ name = "example"
235
+ executable = "example-cli"
236
+
237
+ def launch(self, req: Request, exe: Path, tmp: Path) -> Launch:
238
+ return Launch(argv=(str(exe), "--prompt", req.prompt))
239
+
240
+ def parser(self, req: Request) -> Parser:
241
+ return ExampleParser()
242
+
243
+
244
+ registry = DriverRegistry()
245
+ registry.register("example", ExampleDriver())
246
+ ```
247
+
248
+ `CliDriver` owns process supervision, sequencing, classification, and cleanup. A driver must not add permission flags on behalf of a caller.
249
+
250
+ ## Migrating from 0.1
251
+
252
+ The v2 API has no compatibility layer.
253
+
254
+ - **Renamed:** `Driver` is now `CliDriver`, an abstract base class. Implement `launch()` and `parser()` instead of `check()` and `run()`. `check()` is now a module function taking a driver name and optional `executable`, `env`, `refresh`, and `registry` keywords.
255
+ - **Removed:** `EventHandler` and `SpawnHandler` public aliases. Their callback types appear directly in the `run`, `stream`, and `run_sync` signatures. `Driver` is no longer exported; use `CliDriver`.
256
+ - **Added public names:** `CliDriver`, `ErrorCode`, `Facts`, `FileChanged`, `JsonValue`, `Launch`, `Notice`, `Parser`, `RawEvent`, `RequestError`, and `ReasoningDelta`.
257
+ - **Outcomes and errors:** `Outcome.LIMIT_REACHED` is new for turn and budget limits, which 0.1 reported as retryable. `Diagnostic.code` is an `ErrorCode` instead of a free-form `str`.
258
+ - **Fields added:** `ToolCompleted.tool` and `ToolCompleted.output`; `Result.cost_usd`, `Result.duration_s`, `Result.num_turns`, and `Result.stop_reason`; `Capabilities.version`.
259
+ - **Event schema:** `schema_version` is no longer stored on each event. Import the `SCHEMA_VERSION` module constant instead. `entity_id` contains the CLI-native identifier or `None` when unavailable.
260
+ - **Result behavior:** `Result.text` is the parser's final text when reported, otherwise the last assistant message, rather than the concatenation of all assistant messages. Codex reports `Result.model` as `None`.
261
+ - **Removed behavior:** codex rollout-file parsing is removed; Codex output is parsed from its structured stream.
262
+ - **Registry:** `DriverRegistry.register` accepts only `CliDriver` instances; structurally compatible objects are rejected.
263
+ - **Callbacks and cancel:** `run` accepts synchronous or awaitable `on_event` and `on_spawn` callbacks. `run_sync` now accepts `on_event` and a `threading.Event` for `cancel`.
264
+ - **Request:** `idle_timeout_s` and `raw_events` are available in addition to the 0.1 request fields.
265
+
266
+ ## Known limits
267
+
268
+ - On Windows, Job Object assignment happens immediately after process spawn. In the short window before assignment, the process can exit or spawn descendants; descendants created in that window are outside the job and can survive process-tree cleanup. If creating, configuring, or assigning the Job Object fails, the library silently falls back to `taskkill /T`, which cannot reach a descendant whose parent already exited; no `cleanup_incomplete` warning is reported in that case.
269
+ - Agy receives the prompt in argv, which is limited to 100 KiB by the driver.
270
+ - Agy output that mixes structured JSON events and plain text on stdout is unsupported.
271
+ - Set `idle_timeout_s` longer than the longest tool command, because a quiet but still-running command can reach the idle timeout.
272
+
273
+ ## Contributing
274
+
275
+ Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for setup, the coding standard, and the pull request process. Report security problems privately as described in [SECURITY.md](SECURITY.md). Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md).
276
+
277
+ ```bash
278
+ uv sync
279
+ uv run ruff format --check && uv run ruff check
280
+ uv run pyright
281
+ uv run pytest
282
+ ```
283
+
284
+ ## License
285
+
286
+ [MIT](LICENSE)
287
+
288
+ ## Provenance
289
+
290
+ This library is extracted from the CLI-driver layer of [Refract](https://github.com/sitien173/refract), a multi-model coordination tool. Its drivers, process handling, and many of its tests are ported from Refract under the MIT license.
@@ -0,0 +1,267 @@
1
+ # coding-agent-cli
2
+
3
+ [![CI](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/sitien173/coding-agent-cli/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/coding-agent-cli)](https://pypi.org/project/coding-agent-cli/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/coding-agent-cli)](https://pypi.org/project/coding-agent-cli/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
+
8
+ Run coding-agent CLIs from Python, stream typed events, and get a classified result.
9
+
10
+ Built-in drivers: `claude`, `codex`, `pi`, and `agy`. Python 3.12 or later on Linux, macOS, and Windows. No runtime dependencies. The CLIs themselves are not bundled; install and authenticate each CLI you use.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install coding-agent-cli
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```python
21
+ import asyncio
22
+ from pathlib import Path
23
+
24
+ from coding_agent_cli import Request, run
25
+
26
+
27
+ async def main() -> None:
28
+ result = await run(Request(driver="claude", prompt="Summarize README.md", cwd=Path.cwd()))
29
+ print(result.outcome, result.text)
30
+
31
+
32
+ asyncio.run(main())
33
+ ```
34
+
35
+ ## API
36
+
37
+ The public entry points have these signatures:
38
+
39
+ ```python
40
+ async def run(
41
+ req: Request,
42
+ *,
43
+ on_event: Callable[[Event], Awaitable[None] | None] | None = None,
44
+ cancel: asyncio.Event | None = None,
45
+ on_spawn: Callable[[int], Awaitable[None] | None] | None = None,
46
+ registry: DriverRegistry | None = None,
47
+ ) -> Result: ...
48
+
49
+
50
+ def stream(
51
+ req: Request,
52
+ *,
53
+ cancel: asyncio.Event | None = None,
54
+ on_spawn: Callable[[int], Awaitable[None] | None] | None = None,
55
+ registry: DriverRegistry | None = None,
56
+ ) -> EventStream: ...
57
+
58
+
59
+ def run_sync(
60
+ req: Request,
61
+ *,
62
+ on_event: Callable[[Event], None] | None = None,
63
+ cancel: threading.Event | None = None,
64
+ registry: DriverRegistry | None = None,
65
+ ) -> Result: ...
66
+
67
+
68
+ async def check(
69
+ driver: str,
70
+ *,
71
+ executable: str | None = None,
72
+ env: Mapping[str, str] | None = None,
73
+ refresh: bool = False,
74
+ registry: DriverRegistry | None = None,
75
+ ) -> Capabilities: ...
76
+ ```
77
+
78
+ `run` and `stream` are asynchronous. `run` accepts a sync or async event callback. `stream` is an async iterator; use it as an async context manager so leaving the block closes the run. Callback exceptions propagate after process cleanup. Unknown driver names raise `KeyError`.
79
+
80
+ ```python
81
+ import asyncio
82
+ from pathlib import Path
83
+
84
+ from coding_agent_cli import MessageCompleted, Request, RunCompleted, TextDelta, stream
85
+
86
+
87
+ async def main() -> None:
88
+ req = Request(driver="codex", prompt="List the tests", cwd=Path.cwd())
89
+ async with stream(req) as events:
90
+ async for event in events:
91
+ if isinstance(event, TextDelta):
92
+ print(event.text, end="")
93
+ elif isinstance(event, MessageCompleted):
94
+ print()
95
+ elif isinstance(event, RunCompleted):
96
+ print(event.result.outcome)
97
+
98
+
99
+ asyncio.run(main())
100
+ ```
101
+
102
+ `run_sync` is for synchronous applications and raises `RuntimeError` inside an active event loop. Its `threading.Event` cancellation signal can be set from another thread:
103
+
104
+ ```python
105
+ import threading
106
+ from pathlib import Path
107
+
108
+ from coding_agent_cli import Request, run_sync
109
+
110
+ cancel = threading.Event()
111
+ request = Request(driver="pi", prompt="Inspect this project", cwd=Path.cwd())
112
+ # Pass cancel to run_sync and call cancel.set() from another thread to stop the run.
113
+ result = run_sync(request, cancel=cancel)
114
+ ```
115
+
116
+ `check(driver, ...)` probes `--version` and `--help` and returns `Capabilities(available, executable, version, structured_streaming)`. Successful probe results are cached by executable path and modification time. `run` does not probe first.
117
+
118
+ ## Request and Result
119
+
120
+ `Request` is frozen and keyword-only:
121
+
122
+ | Field | Type | Meaning |
123
+ |---|---|---|
124
+ | `driver` | `str` | Registered driver name: `claude`, `codex`, `pi`, or `agy`. |
125
+ | `prompt` | `str` | Prompt text. |
126
+ | `cwd` | `Path` | Working directory for the CLI. |
127
+ | `session_id` | `str \| None` | Resume a CLI session when supported. |
128
+ | `model` | `str \| None` | Portable model option when supported. |
129
+ | `reasoning` | `str \| None` | Portable reasoning or effort option when supported. |
130
+ | `system_prompt` | `str \| None` | Portable system prompt when supported. |
131
+ | `args` | `tuple[str, ...]` | Additional CLI arguments passed through unchanged. Each driver decides their position; some drivers append the prompt or session arguments after them. |
132
+ | `env` | `Mapping[str, str]` | Environment overlay on the inherited environment. |
133
+ | `timeout_s` | `float \| None` | Positive finite wall-clock limit, or `None` for no limit. |
134
+ | `idle_timeout_s` | `float \| None` | Positive finite idle limit, reset by process output, or `None` for no limit. |
135
+ | `raw_events` | `bool` | Emit parsed JSON stdout lines as `RawEvent` events. |
136
+ | `executable` | `str \| None` | Executable path or name overriding the driver's default. |
137
+
138
+ `Result` is frozen and keyword-only. Its fields are `outcome: Outcome`, `text: str`, `session_id: str | None`, `model: str | None`, `usage: Usage`, `cost_usd: float | None`, `duration_s: float`, `num_turns: int | None`, `stop_reason: str | None`, `returncode: int | None`, `error: Diagnostic | None`, and `warnings: tuple[Diagnostic, ...]`. `text` is the parser's reported final text when the CLI provides one, otherwise the accumulated text of the last assistant message. It is never the concatenation of every assistant turn. Unknown reported values remain `None`. Codex does not report a model, so its `Result.model` is `None`.
139
+
140
+ ## Events
141
+
142
+ Each event has an increasing `sequence` and optional CLI-native `entity_id`; the `entity_id` is `None` when the CLI provides no native identifier. Events have no `schema_version` field. The module constant `SCHEMA_VERSION` is `2`. A run that returns a `Result` emits one final `RunCompleted` after cleanup. If cleanup misses its deadline, the result carries a `cleanup_incomplete` warning and termination is not confirmed. If `on_event` or `on_spawn` raises, or the calling task is cancelled, the library cleans up and re-raises without emitting `RunCompleted`.
143
+
144
+ | Event | `kind` | Additional fields |
145
+ |---|---|---|
146
+ | `SessionIdentified` | `session.identified` | `session_id` |
147
+ | `MessageStarted` | `assistant.message.started` | none |
148
+ | `TextDelta` | `assistant.text.delta` | `text` |
149
+ | `ReasoningDelta` | `assistant.reasoning.delta` | `text` |
150
+ | `MessageCompleted` | `assistant.message.completed` | none |
151
+ | `ToolStarted` | `tool.started` | `tool`, `activity` (`command` or `tool_call`), `input` |
152
+ | `ToolCompleted` | `tool.completed` | `tool`, `status` (`completed`, `error`, or `unknown`), `output` |
153
+ | `FileChanged` | `file.changed` | `path`, `change` (`added`, `modified`, `deleted`, or `unknown`), `evidence` (`reported` or `inferred`) |
154
+ | `Notice` | `notice` | `code`, `message` |
155
+ | `UsageUpdated` | `usage.updated` | `usage` |
156
+ | `RawEvent` | `raw` | `stream`, `data` |
157
+ | `RunCompleted` | `run.completed` | `result` |
158
+
159
+ ## Outcomes and errors
160
+
161
+ | `Outcome` | Meaning |
162
+ |---|---|
163
+ | `success` | The CLI completed successfully. |
164
+ | `retryable` | The run may succeed later, for example after a timeout, service error, nonzero exit, or missing output. |
165
+ | `limit_reached` | A CLI-reported turn or budget limit stopped the run. |
166
+ | `agent_fatal` | The CLI cannot run here, such as when it is missing or authentication fails. |
167
+ | `request_fatal` | The request is invalid or unsupported. |
168
+ | `cancelled` | The caller cancelled the run. |
169
+
170
+ `ErrorCode` values are `bad_cwd`, `invalid_args`, `unsupported_option`, `invalid_prompt`, `missing_cli`, `spawn_failed`, `auth`, `invalid_model`, `context_overflow`, `service_error`, `max_turns`, `budget`, `cancelled`, `timeout`, `idle_timeout`, `stdin_failed`, `execution_error`, `parser_error`, `no_output`, `no_session_id`, `line_too_long`, and `cleanup_incomplete`. `Result.error` is a `Diagnostic(code, message)` when the run has a primary error; `Result.warnings` contains non-fatal diagnostics.
171
+
172
+ `retryable` means possibly transient, not safe to replay. The agent may already have edited files, run commands, or spent tokens. Inspect the working tree before sending the same prompt again.
173
+
174
+ ## CLI arguments and permissions
175
+
176
+ Drivers do not add permission, approval, or sandbox-bypass flags. Callers pass any CLI-specific permission flags through `Request.args`:
177
+
178
+ | CLI | Example flag |
179
+ |---|---|
180
+ | Claude Code | `--permission-mode bypassPermissions` |
181
+ | Codex | `--dangerously-bypass-approvals-and-sandbox` |
182
+ | Pi | No automatic permission flag; `--approve` trusts project-local files and is not a tool-permission bypass. |
183
+ | Agy | `--dangerously-skip-permissions` |
184
+
185
+ Only use unattended permission flags in an environment you are prepared to lose. Portable `model`, `reasoning`, and `system_prompt` options are supported selectively; unsupported options and conflicts with `args` are classified as request errors.
186
+
187
+ ## Custom drivers
188
+
189
+ Subclass `CliDriver` and implement `launch` and `parser`. `Launch` describes argv, optional stdin, and files to read back after the run. `Parser` converts JSON or text lines into events and final `Facts`:
190
+
191
+ ```python
192
+ from collections.abc import Iterable, Mapping
193
+ from pathlib import Path
194
+ from typing import Any, Literal
195
+
196
+ from coding_agent_cli import CliDriver, DriverRegistry, Event, Facts, Launch, Parser, Request
197
+
198
+
199
+ class ExampleParser:
200
+ def feed_json(self, obj: dict[str, Any]) -> Iterable[Event]:
201
+ return ()
202
+
203
+ def feed_text(self, line: str, *, stream: Literal["stdout", "stderr"]) -> Iterable[Event]:
204
+ return ()
205
+
206
+ def finish(self, read_back: Mapping[Path, bytes]) -> Facts:
207
+ return Facts()
208
+
209
+
210
+ class ExampleDriver(CliDriver):
211
+ name = "example"
212
+ executable = "example-cli"
213
+
214
+ def launch(self, req: Request, exe: Path, tmp: Path) -> Launch:
215
+ return Launch(argv=(str(exe), "--prompt", req.prompt))
216
+
217
+ def parser(self, req: Request) -> Parser:
218
+ return ExampleParser()
219
+
220
+
221
+ registry = DriverRegistry()
222
+ registry.register("example", ExampleDriver())
223
+ ```
224
+
225
+ `CliDriver` owns process supervision, sequencing, classification, and cleanup. A driver must not add permission flags on behalf of a caller.
226
+
227
+ ## Migrating from 0.1
228
+
229
+ The v2 API has no compatibility layer.
230
+
231
+ - **Renamed:** `Driver` is now `CliDriver`, an abstract base class. Implement `launch()` and `parser()` instead of `check()` and `run()`. `check()` is now a module function taking a driver name and optional `executable`, `env`, `refresh`, and `registry` keywords.
232
+ - **Removed:** `EventHandler` and `SpawnHandler` public aliases. Their callback types appear directly in the `run`, `stream`, and `run_sync` signatures. `Driver` is no longer exported; use `CliDriver`.
233
+ - **Added public names:** `CliDriver`, `ErrorCode`, `Facts`, `FileChanged`, `JsonValue`, `Launch`, `Notice`, `Parser`, `RawEvent`, `RequestError`, and `ReasoningDelta`.
234
+ - **Outcomes and errors:** `Outcome.LIMIT_REACHED` is new for turn and budget limits, which 0.1 reported as retryable. `Diagnostic.code` is an `ErrorCode` instead of a free-form `str`.
235
+ - **Fields added:** `ToolCompleted.tool` and `ToolCompleted.output`; `Result.cost_usd`, `Result.duration_s`, `Result.num_turns`, and `Result.stop_reason`; `Capabilities.version`.
236
+ - **Event schema:** `schema_version` is no longer stored on each event. Import the `SCHEMA_VERSION` module constant instead. `entity_id` contains the CLI-native identifier or `None` when unavailable.
237
+ - **Result behavior:** `Result.text` is the parser's final text when reported, otherwise the last assistant message, rather than the concatenation of all assistant messages. Codex reports `Result.model` as `None`.
238
+ - **Removed behavior:** codex rollout-file parsing is removed; Codex output is parsed from its structured stream.
239
+ - **Registry:** `DriverRegistry.register` accepts only `CliDriver` instances; structurally compatible objects are rejected.
240
+ - **Callbacks and cancel:** `run` accepts synchronous or awaitable `on_event` and `on_spawn` callbacks. `run_sync` now accepts `on_event` and a `threading.Event` for `cancel`.
241
+ - **Request:** `idle_timeout_s` and `raw_events` are available in addition to the 0.1 request fields.
242
+
243
+ ## Known limits
244
+
245
+ - On Windows, Job Object assignment happens immediately after process spawn. In the short window before assignment, the process can exit or spawn descendants; descendants created in that window are outside the job and can survive process-tree cleanup. If creating, configuring, or assigning the Job Object fails, the library silently falls back to `taskkill /T`, which cannot reach a descendant whose parent already exited; no `cleanup_incomplete` warning is reported in that case.
246
+ - Agy receives the prompt in argv, which is limited to 100 KiB by the driver.
247
+ - Agy output that mixes structured JSON events and plain text on stdout is unsupported.
248
+ - Set `idle_timeout_s` longer than the longest tool command, because a quiet but still-running command can reach the idle timeout.
249
+
250
+ ## Contributing
251
+
252
+ Contributions are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) for setup, the coding standard, and the pull request process. Report security problems privately as described in [SECURITY.md](SECURITY.md). Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md).
253
+
254
+ ```bash
255
+ uv sync
256
+ uv run ruff format --check && uv run ruff check
257
+ uv run pyright
258
+ uv run pytest
259
+ ```
260
+
261
+ ## License
262
+
263
+ [MIT](LICENSE)
264
+
265
+ ## Provenance
266
+
267
+ This library is extracted from the CLI-driver layer of [Refract](https://github.com/sitien173/refract), a multi-model coordination tool. Its drivers, process handling, and many of its tests are ported from Refract under the MIT license.