dev-autopilot 0.4.0__tar.gz → 0.5.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 (153) hide show
  1. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/PKG-INFO +31 -19
  2. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/README.md +30 -18
  3. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/__init__.py +1 -1
  4. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/backends/__init__.py +5 -1
  5. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/backends/command.py +38 -8
  6. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/cli.py +76 -23
  7. dev_autopilot-0.5.0/autopilot/commands/precheck.py +216 -0
  8. dev_autopilot-0.5.0/autopilot/commands/setup.py +265 -0
  9. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/config.py +8 -0
  10. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/doctor.py +166 -48
  11. dev_autopilot-0.5.0/autopilot/install_errors.py +195 -0
  12. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/__pycache__/cloudflare_apply.cpython-313.pyc +0 -0
  13. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/project.yaml +1 -1
  14. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/upgrade.py +69 -3
  15. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/dev_autopilot.egg-info/PKG-INFO +31 -19
  16. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/dev_autopilot.egg-info/SOURCES.txt +7 -0
  17. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/pyproject.toml +1 -1
  18. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_doctor.py +3 -0
  19. dev_autopilot-0.5.0/tests/test_install_errors.py +104 -0
  20. dev_autopilot-0.5.0/tests/test_install_scripts.py +151 -0
  21. dev_autopilot-0.5.0/tests/test_precheck.py +120 -0
  22. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_presets.py +75 -2
  23. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_quickstart.py +9 -7
  24. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_runner_detect.py +146 -12
  25. dev_autopilot-0.5.0/tests/test_setup.py +238 -0
  26. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_simple_view.py +2 -2
  27. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_upgrade.py +67 -2
  28. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_version.py +32 -4
  29. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/LICENSE +0 -0
  30. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/__main__.py +0 -0
  31. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/backends/claude_cli.py +0 -0
  32. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/commands/__init__.py +0 -0
  33. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/commands/preflight.py +0 -0
  34. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/context.py +0 -0
  35. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/dash_term.py +0 -0
  36. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/dash_web.py +0 -0
  37. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/dashboard.py +0 -0
  38. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/decisions.py +0 -0
  39. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/deploy.py +0 -0
  40. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/docs.py +0 -0
  41. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/gate.py +0 -0
  42. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/github_util.py +0 -0
  43. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/gitops.py +0 -0
  44. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/growth.py +0 -0
  45. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/handoff.py +0 -0
  46. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/layers.py +0 -0
  47. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/log_scrub.py +0 -0
  48. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/mcp.py +0 -0
  49. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/notify.py +0 -0
  50. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/orchestrator.py +0 -0
  51. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/plain.py +0 -0
  52. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/plan.py +0 -0
  53. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/proc.py +0 -0
  54. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/auditor.md +0 -0
  55. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/decide.md +0 -0
  56. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/docs.md +0 -0
  57. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/fixer.md +0 -0
  58. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/onboard.md +0 -0
  59. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/plan_writer.md +0 -0
  60. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/replanner.md +0 -0
  61. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/research.md +0 -0
  62. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/resume.md +0 -0
  63. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/retry.md +0 -0
  64. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/review.md +0 -0
  65. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/system.md +0 -0
  66. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/task.md +0 -0
  67. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/triage.md +0 -0
  68. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/unstick.md +0 -0
  69. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/prompts/verify.md +0 -0
  70. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/repomap.py +0 -0
  71. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/report.py +0 -0
  72. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/rights.py +0 -0
  73. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/schemas.py +0 -0
  74. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/state.py +0 -0
  75. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/BRAIN.md +0 -0
  76. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/CLAUDE.md.snippet +0 -0
  77. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/DECISIONS.md +0 -0
  78. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/HANDOFF.md +0 -0
  79. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/gitignore +0 -0
  80. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/ide_rules.md +0 -0
  81. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/agent/plan.yaml +0 -0
  82. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/cloudflare_apply.py +0 -0
  83. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/deploy.sh +0 -0
  84. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/email_verify.py +0 -0
  85. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/infra/cloudflare.yaml +0 -0
  86. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/docker.yaml +0 -0
  87. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/generic.yaml +0 -0
  88. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/go.yaml +0 -0
  89. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/java.yaml +0 -0
  90. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/node.yaml +0 -0
  91. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/python.yaml +0 -0
  92. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/rust.yaml +0 -0
  93. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/autopilot/templates/presets/static.yaml +0 -0
  94. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/dev_autopilot.egg-info/dependency_links.txt +0 -0
  95. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/dev_autopilot.egg-info/entry_points.txt +0 -0
  96. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/dev_autopilot.egg-info/requires.txt +0 -0
  97. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/dev_autopilot.egg-info/top_level.txt +0 -0
  98. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/setup.cfg +0 -0
  99. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_adopt.py +0 -0
  100. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_autofix.py +0 -0
  101. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_autopilot.py +0 -0
  102. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_backend.py +0 -0
  103. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_cache.py +0 -0
  104. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_chat.py +0 -0
  105. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_conflicts.py +0 -0
  106. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_consolidate.py +0 -0
  107. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_dash_term.py +0 -0
  108. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_dash_web.py +0 -0
  109. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_dashboard.py +0 -0
  110. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_dashboard_rows.py +0 -0
  111. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_dashboard_time.py +0 -0
  112. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_decide.py +0 -0
  113. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_deploy.py +0 -0
  114. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_deps.py +0 -0
  115. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_docs_session.py +0 -0
  116. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_functional.py +0 -0
  117. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_gate.py +0 -0
  118. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_github_identity.py +0 -0
  119. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_handoff.py +0 -0
  120. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_heal.py +0 -0
  121. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_ide_rules.py +0 -0
  122. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_infra_templates.py +0 -0
  123. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_intake.py +0 -0
  124. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_killed_usage.py +0 -0
  125. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_layers.py +0 -0
  126. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_lean.py +0 -0
  127. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_lifecycle.py +0 -0
  128. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_mcp.py +0 -0
  129. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_needs_you.py +0 -0
  130. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_new_file_gate.py +0 -0
  131. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_opening.py +0 -0
  132. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_orchestrator.py +0 -0
  133. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_parallel.py +0 -0
  134. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_plan_source.py +0 -0
  135. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_preflight.py +0 -0
  136. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_proc.py +0 -0
  137. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_prompt_contract_task.py +0 -0
  138. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_quality.py +0 -0
  139. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_reliability.py +0 -0
  140. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_repomap.py +0 -0
  141. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_research.py +0 -0
  142. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_review.py +0 -0
  143. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_rights.py +0 -0
  144. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_roadmap.py +0 -0
  145. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_routing.py +0 -0
  146. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_sandbox.py +0 -0
  147. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_stats.py +0 -0
  148. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_status_line.py +0 -0
  149. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_status_page.py +0 -0
  150. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_sync.py +0 -0
  151. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_three_actions.py +0 -0
  152. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_usage.py +0 -0
  153. {dev_autopilot-0.4.0 → dev_autopilot-0.5.0}/tests/test_window.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dev-autopilot
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Autopilot builds your software project while you are away. You write what you want in plain words and start it once. You get working software, tested, with a record of every step.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/manishjnv/AutoPilot
@@ -46,8 +46,7 @@ It is for one developer, or for a person with an idea and a developer friend.
46
46
  | Questions | The chat waits for you. | That task waits. Other tasks continue. |
47
47
  | Status | You scroll the chat. | `ap watch` shows tasks, output, progress and usage. |
48
48
 
49
- The [full comparison](ARCHITECTURE.md#13-claude-code-alone-and-with-autopilot) has ten rows. Use Claude Code alone
50
- for a small change, a question, or an open design.
49
+ The [full comparison](ARCHITECTURE.md#13-claude-code-alone-and-with-autopilot) has ten rows.
51
50
 
52
51
  ## How it works
53
52
 
@@ -95,28 +94,41 @@ Two real projects built from a plan:
95
94
  | Agent time | 2.2 hours, 38 sessions | 1.4 hours, 42 sessions |
96
95
  | Cost at API prices | $17.13 | $19.01 |
97
96
 
98
- Both runs used a subscription. The envguard audits found 8 real bugs; Autopilot fixed all. envguard is private; numbers come from the run log. A lean rerun of envguard used 39.5 percent fewer tokens per task.
97
+ Both runs used a subscription. The envguard audits found 8 real bugs; Autopilot fixed all. envguard is private; numbers come from the run log.
99
98
 
100
99
  ## Install
101
100
 
102
- One line. It installs what is missing for your user, with no administrator prompt.
103
- Windows (PowerShell):
101
+ Three steps, no administrator prompt.
104
102
 
105
- ```powershell
106
- irm https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.ps1 | iex
107
- ```
103
+ 1. Run one line in your shell.
108
104
 
109
- macOS, Linux or WSL:
105
+ Windows (PowerShell):
110
106
 
111
- ```bash
112
- curl -fsSL https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.sh | bash
113
- ```
107
+ ```powershell
108
+ irm https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.ps1 | iex
109
+ ```
110
+
111
+ macOS, Linux, WSL or Git Bash:
112
+
113
+ ```bash
114
+ curl -fsSL https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.sh | bash
115
+ ```
116
+
117
+ Windows (CMD):
118
+
119
+ ```bat
120
+ curl -o %TEMP%\install.cmd https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.cmd && %TEMP%\install.cmd
121
+ ```
122
+
123
+ Have Python 3.10 or newer? `pip install dev-autopilot && ap setup`.
124
+
125
+ 2. Answer one question: which AI do you pay for (Claude, GitHub Copilot, Codex, Cursor or Kiro)? The script
126
+ installs that one CLI and prints its login command. Copilot in VS Code is not a command-line tool. Autopilot
127
+ needs the Copilot CLI, which uses the same subscription.
128
+ 3. Open a project folder and type `ap`.
114
129
 
115
- On Windows pick Windows or WSL, not both; WSL only for the sandbox. With [uv](https://docs.astral.sh/uv/) already
116
- installed, `uv tool install dev-autopilot` is the same.
117
- Open a project folder and type `ap`. The first run logs you in to Claude (a paid plan, Pro or Max), then starts.
118
- See [What `ap` does](HOWTO.md#what-ap-does-in-a-folder). After a release, run `ap upgrade`. For a repair or a
119
- second machine, run `ap install`. Checksums and each step: [Install with one script](HOWTO.md#install-with-one-script).
130
+ See [What `ap` does](HOWTO.md#what-ap-does-in-a-folder). Autopilot updates itself once a day; `ap uninstall --all`
131
+ removes it. Checksums and each step: [Install with one script](HOWTO.md#install-with-one-script).
120
132
 
121
133
  ## Example output
122
134
 
@@ -144,5 +156,5 @@ audit: completion audit: 92% complete, 8 corrective tasks (FIX001)
144
156
  > Use a sandbox on a server: sessions run without permission prompts. See
145
157
  > [Run Autopilot on a server](HOWTO.md#run-autopilot-on-a-server).
146
158
 
147
- - Version 0.3.0, an early release. [MIT license](LICENSE).
159
+ - Version 0.4.0, an early release. [MIT license](LICENSE).
148
160
  - One CLI agent per session. See [limits and runners](HOWTO.md#limits-and-supported-agent-clis).
@@ -19,8 +19,7 @@ It is for one developer, or for a person with an idea and a developer friend.
19
19
  | Questions | The chat waits for you. | That task waits. Other tasks continue. |
20
20
  | Status | You scroll the chat. | `ap watch` shows tasks, output, progress and usage. |
21
21
 
22
- The [full comparison](ARCHITECTURE.md#13-claude-code-alone-and-with-autopilot) has ten rows. Use Claude Code alone
23
- for a small change, a question, or an open design.
22
+ The [full comparison](ARCHITECTURE.md#13-claude-code-alone-and-with-autopilot) has ten rows.
24
23
 
25
24
  ## How it works
26
25
 
@@ -68,28 +67,41 @@ Two real projects built from a plan:
68
67
  | Agent time | 2.2 hours, 38 sessions | 1.4 hours, 42 sessions |
69
68
  | Cost at API prices | $17.13 | $19.01 |
70
69
 
71
- Both runs used a subscription. The envguard audits found 8 real bugs; Autopilot fixed all. envguard is private; numbers come from the run log. A lean rerun of envguard used 39.5 percent fewer tokens per task.
70
+ Both runs used a subscription. The envguard audits found 8 real bugs; Autopilot fixed all. envguard is private; numbers come from the run log.
72
71
 
73
72
  ## Install
74
73
 
75
- One line. It installs what is missing for your user, with no administrator prompt.
76
- Windows (PowerShell):
74
+ Three steps, no administrator prompt.
77
75
 
78
- ```powershell
79
- irm https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.ps1 | iex
80
- ```
76
+ 1. Run one line in your shell.
81
77
 
82
- macOS, Linux or WSL:
78
+ Windows (PowerShell):
83
79
 
84
- ```bash
85
- curl -fsSL https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.sh | bash
86
- ```
80
+ ```powershell
81
+ irm https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.ps1 | iex
82
+ ```
83
+
84
+ macOS, Linux, WSL or Git Bash:
85
+
86
+ ```bash
87
+ curl -fsSL https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.sh | bash
88
+ ```
89
+
90
+ Windows (CMD):
91
+
92
+ ```bat
93
+ curl -o %TEMP%\install.cmd https://raw.githubusercontent.com/manishjnv/AutoPilot/main/install.cmd && %TEMP%\install.cmd
94
+ ```
95
+
96
+ Have Python 3.10 or newer? `pip install dev-autopilot && ap setup`.
97
+
98
+ 2. Answer one question: which AI do you pay for (Claude, GitHub Copilot, Codex, Cursor or Kiro)? The script
99
+ installs that one CLI and prints its login command. Copilot in VS Code is not a command-line tool. Autopilot
100
+ needs the Copilot CLI, which uses the same subscription.
101
+ 3. Open a project folder and type `ap`.
87
102
 
88
- On Windows pick Windows or WSL, not both; WSL only for the sandbox. With [uv](https://docs.astral.sh/uv/) already
89
- installed, `uv tool install dev-autopilot` is the same.
90
- Open a project folder and type `ap`. The first run logs you in to Claude (a paid plan, Pro or Max), then starts.
91
- See [What `ap` does](HOWTO.md#what-ap-does-in-a-folder). After a release, run `ap upgrade`. For a repair or a
92
- second machine, run `ap install`. Checksums and each step: [Install with one script](HOWTO.md#install-with-one-script).
103
+ See [What `ap` does](HOWTO.md#what-ap-does-in-a-folder). Autopilot updates itself once a day; `ap uninstall --all`
104
+ removes it. Checksums and each step: [Install with one script](HOWTO.md#install-with-one-script).
93
105
 
94
106
  ## Example output
95
107
 
@@ -117,5 +129,5 @@ audit: completion audit: 92% complete, 8 corrective tasks (FIX001)
117
129
  > Use a sandbox on a server: sessions run without permission prompts. See
118
130
  > [Run Autopilot on a server](HOWTO.md#run-autopilot-on-a-server).
119
131
 
120
- - Version 0.3.0, an early release. [MIT license](LICENSE).
132
+ - Version 0.4.0, an early release. [MIT license](LICENSE).
121
133
  - One CLI agent per session. See [limits and runners](HOWTO.md#limits-and-supported-agent-clis).
@@ -1,4 +1,4 @@
1
1
  """Autopilot — autonomous, plan-driven development orchestrator for Claude Code."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.0"
4
4
  AGENT_DIR = ".agent"
@@ -10,6 +10,10 @@ from datetime import datetime, timedelta
10
10
  LIMIT_RX = re.compile(r"^\s*(?:you've|you have) hit your (?:[\w-]+ ){0,3}limit"
11
11
  r"(?:\s*[·•|\-–—,.]*\s*resets?\s+(?P<reset>[^\n]+))?", re.I | re.M)
12
12
  API_LIMIT_RX = re.compile(r"^\s*API Error: (?:429|529)\b|^\s*API Error:.*(?:rate_limit_error|overloaded_error)", re.I | re.M)
13
+ # Q2 copilot: the premium-request quota message. Docs checked 2026-10-12, not run live: the exact wording and the exit
14
+ # code are recorded in Q9; until then any line that names premium requests with a limit word counts.
15
+ COPILOT_LIMIT_RX = re.compile(r"^\s*.{0,60}\b(?:premium requests? (?:limit|quota)|(?:limit|quota) (?:of|for|on) premium requests?"
16
+ r"|premium requests? .{0,20}\b(?:exhausted|exceeded|used up))", re.I | re.M)
13
17
  _RESET_RX = re.compile(r"^(?:(?P<mon>[a-z]{3,9})\.?\s+(?P<day>\d{1,2})\s*(?:,|at)?\s*)?(?P<h>\d{1,2})(?::(?P<m>\d{2}))?"
14
18
  r"\s*(?P<ap>am|pm)?\s*(?:\((?P<tz>[^)]+)\))?$", re.I)
15
19
  _MONTHS = {m: i + 1 for i, m in enumerate("jan feb mar apr may jun jul aug sep oct nov dec".split())}
@@ -57,7 +61,7 @@ def detect_limit(text: str, now: datetime | None = None) -> tuple[bool, float |
57
61
  m = LIMIT_RX.search(text)
58
62
  if m:
59
63
  return True, (parse_reset(m["reset"], now or datetime.now().astimezone()) if m["reset"] else None)
60
- return bool(API_LIMIT_RX.search(text)), None
64
+ return bool(API_LIMIT_RX.search(text) or COPILOT_LIMIT_RX.search(text)), None
61
65
 
62
66
 
63
67
  # Failures of the machinery, not of the work: retried by the orchestrator, never counted as a task attempt.
@@ -1,8 +1,10 @@
1
1
  """Generic backend: any CLI agent (Aider, Codex, a LiteLLM wrapper...). Prompt on stdin, report in stdout."""
2
2
  from __future__ import annotations
3
3
 
4
+ import os
4
5
  import re
5
6
  import shlex
7
+ import shutil
6
8
  import tempfile
7
9
  from pathlib import Path
8
10
 
@@ -21,30 +23,58 @@ PRESETS = {
21
23
  # https://cursor.com/docs/cli/headless , https://cursor.com/docs/cli/reference/parameters ,
22
24
  # https://aider.chat/docs/config/options.html , https://aider.chat/docs/scripting.html
23
25
  # kiro: no _ASK, because a positional argument stops kiro-cli from reading the prompt on stdin.
24
- # cursor: stdin is not documented (the Chief project, PR #48, uses it), so the short _ASK argument stays.
26
+ # cursor: stdin is not documented (the Chief project, PR #48, uses it), so the short _ASK argument stays. The binary
27
+ # is `agent` since the rename (docs checked 2026-10-12); `command_template` falls back to an installed `cursor-agent`.
25
28
  # aider: no stdin prompt, so it reads {prompt_file} (a temp file outside the worktree); git stays with the orchestrator.
26
29
  # --trust-all-tools (kiro) and --force (cursor) approve every tool, not only the workspace (like gemini yolo).
27
30
  "kiro": "kiro-cli chat --no-interactive --trust-all-tools{model_flag}",
28
- "cursor": "cursor-agent -p --force --trust{model_flag} " + _ASK,
31
+ "cursor": "agent -p --force --trust{model_flag} " + _ASK,
29
32
  "aider": "aider --message-file {prompt_file} --yes-always --no-auto-commits --no-dirty-commits --no-gitignore"
30
33
  " --no-check-update --no-analytics --no-pretty --no-stream{model_flag}",
34
+ # Q2 copilot: docs checked 2026-10-12, not run live (the live task is Q9). Sources: https://docs.github.com/en/copilot
35
+ # /how-tos/use-copilot-agents/use-copilot-cli . `-p` takes the prompt as an argument (stdin is not documented), so the
36
+ # prompt goes to a temp file outside the worktree (the aider pattern) and `--add-dir` lets copilot read that folder.
37
+ # --allow-all-tools and --no-ask-user approve every tool and never wait for a person; -s is the silent output.
38
+ "copilot": 'copilot -p "Your full instructions are in the file {prompt_path}. Read it, follow it, end with the '
39
+ 'requested JSON report." --allow-all-tools --no-ask-user -s --add-dir {prompt_dir}{model_flag}',
31
40
  }
32
- _MODEL_OPT = {"kiro": "--model", "cursor": "--model", "aider": "--model"} # P03-T01: aider -m means --message
41
+ _MODEL_OPT = {"kiro": "--model", "cursor": "--model", "aider": "--model", "copilot": "--model"} # aider -m = --message
33
42
  CLAUDE_ALIASES = ("haiku", "sonnet", "opus")
43
+ # Q2: a preset's own names for the ladder aliases, used when agent.model_map does not map them. Copilot accepts Claude
44
+ # model names (docs checked 2026-10-12, not run live: only `claude-haiku-4.5` is named in the docs; sonnet and opus stay
45
+ # unmapped = Copilot's default model until Q9 records the exact names).
46
+ DEFAULT_MODEL_MAP = {"copilot": {"haiku": "claude-haiku-4.5"}}
47
+ _FILE_KEYS = ("{prompt_file}", "{prompt_path}", "{prompt_dir}")
34
48
  SAFE_MODEL = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/@-]{0,127}")
35
49
 
36
50
 
37
51
  def command_template(cfg) -> str:
38
- return cfg.get("agent.command") or PRESETS.get(str(cfg.get("agent.preset") or ""), "")
52
+ """The shell template: `agent.command`, or the preset. When the preset's binary is not on PATH but an older name
53
+ of it is (the doctor table's aliases, for example `cursor-agent`), that name is used."""
54
+ own = cfg.get("agent.command")
55
+ if own:
56
+ return own
57
+ preset = str(cfg.get("agent.preset") or "")
58
+ tpl = PRESETS.get(preset, "")
59
+ if tpl:
60
+ from ..doctor import runner
61
+ row = runner(preset)
62
+ if row is not None and not shutil.which(row.binary):
63
+ alias = next((a for a in row.aliases if shutil.which(a)), "")
64
+ if alias:
65
+ tpl = alias + tpl[len(row.binary):]
66
+ return tpl
39
67
 
40
68
 
41
69
  def model_flag(cfg, model: str) -> str:
42
70
  """` -m <model>` (` --model <model>` for kiro, cursor, aider) after `agent.model_map` (ladder names → this CLI's
43
71
  models); an unmapped Claude alias gets no flag, so the CLI uses its own default model."""
44
- m = str((cfg.get("agent.model_map", {}) or {}).get(model, model))
72
+ preset = str(cfg.get("agent.preset") or "")
73
+ own = (cfg.get("agent.model_map", {}) or {})
74
+ m = str(own.get(model, DEFAULT_MODEL_MAP.get(preset, {}).get(model, model)))
45
75
  if not SAFE_MODEL.fullmatch(m): # the command runs through a shell (sh or cmd.exe): no quoting tricks needed
46
76
  raise ValueError(f"unsafe model name for agent.preset: {m!r}")
47
- opt = _MODEL_OPT.get(str(cfg.get("agent.preset") or ""), "-m")
77
+ opt = _MODEL_OPT.get(preset, "-m")
48
78
  return "" if m in CLAUDE_ALIASES else f" {opt} {m}"
49
79
 
50
80
 
@@ -57,7 +87,7 @@ class CommandBackend:
57
87
  # the project context lives in system_append (U7); a generic CLI has no system prompt, so it leads the prompt
58
88
  prompt = f"{req.system_append}\n\n---\n\n{req.prompt}" if req.system_append else req.prompt
59
89
  pf = ""
60
- if "{prompt_file}" in self.template: # P03-T01: for CLIs without a stdin prompt (aider); outside the worktree
90
+ if any(k in self.template for k in _FILE_KEYS): # P03-T01: CLIs without a stdin prompt (aider, copilot); outside the worktree
61
91
  with tempfile.NamedTemporaryFile("w", encoding="utf-8", prefix="autopilot-prompt-", suffix=".md",
62
92
  delete=False) as f:
63
93
  f.write(prompt)
@@ -70,7 +100,7 @@ class CommandBackend:
70
100
  def _run(self, req: SessionRequest, prompt: str, pf: str) -> SessionResult:
71
101
  cmd = self.template.format(model=shlex.quote(req.model), budget=req.budget_usd or 0,
72
102
  read_only=int(req.read_only), model_flag=model_flag(self.cfg, req.model),
73
- prompt_file=f'"{pf}"')
103
+ prompt_file=f'"{pf}"', prompt_path=pf, prompt_dir=f'"{os.path.dirname(pf)}"')
74
104
  env = {**agent_env(self.cfg), "AUTOPILOT_MODEL": req.model, "AUTOPILOT_READ_ONLY": str(int(req.read_only)),
75
105
  "AUTOPILOT_SYSTEM_APPEND": req.system_append}
76
106
  env.update({"AUTODEV_" + k[10:]: v for k, v in list(env.items()) if k.startswith("AUTOPILOT_")})
@@ -383,36 +383,49 @@ def remember_setup(proj_path: Path, cmds: list[str]) -> None:
383
383
 
384
384
 
385
385
  def runner_set(root: Path) -> bool:
386
- """N3: does the project's own project.yaml choose the agent CLI? Read raw: Config merges the claude_cli default.
387
- An unusual `agent:` value (not a mapping) counts as set, so the file is left alone."""
388
- path = Path(root) / AGENT_DIR / "project.yaml"
389
- try:
390
- agent = (load_yaml(path) if path.exists() else {}).get("agent") or {}
391
- except (yaml.YAMLError, AttributeError):
392
- return True # a file preflight cannot read is never edited; validate reports it
393
- return not isinstance(agent, dict) or any(agent.get(k) for k in ("backend", "command", "preset"))
386
+ """N3: does the project's own project.yaml choose the agent CLI? One reader: `doctor.runner_chosen`."""
387
+ from .doctor import runner_chosen
388
+ return runner_chosen(root)
394
389
 
395
390
 
396
391
  def pick_runner(root: Path) -> bool:
397
- """N3: no runner set -> write the first installed, logged-in agent CLI on PATH. False = none (install hint printed).
398
- A claude that is installed but logged out stays the default with no write, so the H2 login offer still runs."""
392
+ """N3: no runner set -> write the first installed, logged-in agent CLI on PATH. False = none: the neutral list of
393
+ every runner with its install command (Q1), never 'Install Claude Code' first. A claude that is installed but
394
+ logged out stays the default with no write, so the H2 login offer still runs. The runner `ap setup` chose on this
395
+ PC (Q3, `~/.autopilot/runner`) is written when it is installed and nothing else is logged in."""
399
396
  from .backends.claude_cli import ClaudeCLIBackend
400
- from .doctor import INSTALL, RUNNERS, detect_runner
397
+ from .doctor import detect_runner, installed_runners, no_runner_text, runner
401
398
  if os.environ.get("AUTOPILOT_CLAUDE_BIN") or os.environ.get("AUTODEV_CLAUDE_BIN"):
402
399
  return True # an explicit claude binary is a choice: keep the claude_cli default
403
400
  choice = detect_runner()
401
+ if not choice:
402
+ row = runner(setup_choice())
403
+ if row is not None and row.which():
404
+ choice = row.key
404
405
  if choice:
405
406
  if not remember_runner(Path(root) / AGENT_DIR / "project.yaml", choice):
406
407
  return False
407
- print(f"runner: {next(b for b, be, *_ in RUNNERS if be == choice)} (found on PATH)")
408
+ print(f"runner: {runner(choice).binary} (found on PATH)")
408
409
  return True
409
410
  if shutil.which(ClaudeCLIBackend.resolve_binary()):
410
411
  return True
411
- print(f"FAIL no agent CLI found. Install Claude Code with: {INSTALL['native'][1]}\n"
412
- f" supported: {', '.join(b for b, *_ in RUNNERS)}")
412
+ found = installed_runners()
413
+ if found: # installed, logged out: say which login is missing, nothing else
414
+ row = found[0][0]
415
+ print(f"FAIL {row.binary} is installed but not logged in: run `{row.login}`, then start again")
416
+ return False
417
+ print(f"FAIL {no_runner_text()}")
413
418
  return False
414
419
 
415
420
 
421
+ def setup_choice() -> str:
422
+ """Q3: the runner `ap setup` chose on this PC (one word in `~/.autopilot/runner`), '' when none."""
423
+ try:
424
+ return (_recent_file().with_name("runner").read_text(encoding="utf-8").strip())
425
+ except OSError:
426
+ return ""
427
+
428
+
416
429
  def remember_runner(proj_path: Path, choice: str) -> bool:
417
430
  """N3: write agent.backend (and agent.preset) as a text edit after the `agent:` line, so comments stay. The new
418
431
  text is parsed first: when it would not give exactly the wanted values, nothing is written."""
@@ -1379,12 +1392,37 @@ def cmd_approve(args):
1379
1392
  return 0
1380
1393
 
1381
1394
 
1382
- def cmd_uninstall(args):
1383
- """N8: `ap uninstall --rights` removes the scheduled task / the sudoers file the installer created."""
1384
- from .rights import remove_rights
1395
+ def cmd_uninstall(args, spawn=None):
1396
+ """N8: `ap uninstall --rights` removes the scheduled task / the sudoers file the installer created.
1397
+ T3: `ap uninstall --all` removes the rights, `~/.autopilot`, and the package itself (last, after this process has
1398
+ exited: `ap.exe` is locked while it runs). `--dry-run` lists the steps. Projects' `.agent/` folders and the AI CLIs stay."""
1399
+ from .rights import have_rights, remove_rights
1400
+ from .upgrade import detect_install, spawn_after_exit
1401
+ dry = getattr(args, "dry_run", False)
1402
+ if getattr(args, "all", False):
1403
+ say = (lambda s: print(("would " if dry else "") + s))
1404
+ if have_rights():
1405
+ say("remove the rights the installer took (scheduled task / sudoers file)")
1406
+ if not dry:
1407
+ for line in remove_rights():
1408
+ print(line)
1409
+ home = _recent_file().parent
1410
+ say(f"delete {home} (recent projects, the install log, the runner choice)")
1411
+ if not dry and home.is_dir():
1412
+ shutil.rmtree(home, ignore_errors=True)
1413
+ kind = detect_install()
1414
+ cmd = {"uv": ["uv", "tool", "uninstall", "dev-autopilot"], "pip": [sys.executable, "-m", "pip", "uninstall", "-y", "dev-autopilot"]}.get(kind)
1415
+ if cmd is None:
1416
+ say("remove the package: this is a git checkout; run `pip uninstall dev-autopilot`, then delete the folder")
1417
+ else:
1418
+ say(f"remove the package ({kind}), after this command has exited: {' '.join(cmd)}")
1419
+ if not dry:
1420
+ (spawn or spawn_after_exit)(cmd)
1421
+ print("Kept: uv, Python, git, your AI CLI and its login, and each project's .agent/ folder (delete it to start clean).")
1422
+ return 0
1385
1423
  if not getattr(args, "rights", False):
1386
1424
  print("ap uninstall --rights removes the rights the installer took (scheduled task / sudoers file)\n"
1387
- "uv tool uninstall dev-autopilot removes Autopilot itself")
1425
+ "ap uninstall --all removes the rights, ~/.autopilot and Autopilot itself (--dry-run lists the steps)")
1388
1426
  return 0
1389
1427
  for line in remove_rights():
1390
1428
  print(line)
@@ -1479,7 +1517,7 @@ def pick_project() -> str | None:
1479
1517
  print("This folder is no Autopilot project, and no other project is known on this PC.\n"
1480
1518
  "Go to the project folder, or name it with -C <folder>. To start a new one: autopilot quickstart")
1481
1519
  return None
1482
- print(f"project: {chosen}")
1520
+ print(f"this folder is no project, using the last one used: {chosen}") # Q10: say why, not only which
1483
1521
  return str(chosen)
1484
1522
 
1485
1523
 
@@ -1499,6 +1537,8 @@ def main(argv=None):
1499
1537
  if hasattr(s, "reconfigure"):
1500
1538
  s.reconfigure(encoding="utf-8", errors="replace")
1501
1539
  called = Path(sys.argv[0]).stem.lower() if argv is None else ""
1540
+ if called not in ("ap", "autopilot"): # `python -m autopilot` arrives as `__main__`: never a name a user reads
1541
+ called = ""
1502
1542
  argv = list(sys.argv[1:] if argv is None else argv)
1503
1543
  if argv[:1] == ["start"]: # J2: one plain word to start or continue the build in the background
1504
1544
  argv[:1] = ["run", "--clear-stop", "--detach"]
@@ -1567,13 +1607,20 @@ def main(argv=None):
1567
1607
  p = add("skip", cmd_skip, "skip task(s)"); p.add_argument("task_ids", nargs="+")
1568
1608
  p = add("approve", cmd_approve, "deploy an approved phase to prod"); p.add_argument("phase")
1569
1609
  add("mcp", cmd_mcp, "MCP server on stdin/stdout for IDE chats: ap_status, ap_next, ap_start, ap_stop, ap_answer")
1570
- p = add("uninstall", cmd_uninstall, "--rights: remove the scheduled task / sudoers file the installer created")
1571
- p.add_argument("--rights", action="store_true")
1610
+ p = add("uninstall", cmd_uninstall, "--rights: remove the rights the installer took; --all: remove Autopilot from this PC")
1611
+ p.add_argument("--rights", action="store_true"); p.add_argument("--all", action="store_true")
1612
+ p.add_argument("--dry-run", action="store_true", help="list the steps, change nothing")
1572
1613
  from .upgrade import cmd_install, cmd_upgrade
1573
1614
  p = add("upgrade", cmd_upgrade, "get the newest Autopilot; --check only prints")
1574
1615
  p.add_argument("--check", action="store_true"); p.add_argument("--ref"); p.add_argument("--daily", action="store_true", help=argparse.SUPPRESS)
1575
1616
  p = add("install", cmd_install, "install or repair Autopilot for this user; --rights = the one rights step")
1576
1617
  p.add_argument("--rights", action="store_true")
1618
+ from .commands.setup import cmd_setup
1619
+ p = add("setup", cmd_setup, "find the AI you pay for, install its CLI, check the login, write the choice (one question at most)")
1620
+ p.add_argument("--dry-run", action="store_true", help="scan and print the plan; install and write nothing")
1621
+ p.add_argument("--runner", help="answer the question in advance: claude, copilot, codex, cursor or kiro")
1622
+ p.add_argument("--yes", action="store_true", help="take the default answer")
1623
+ p.add_argument("--skip-login", action="store_true", help="do not check or start a login")
1577
1624
  add("stop", cmd_stop, "graceful stop before the next session")
1578
1625
  add("resume", cmd_resume, "clear the stop flag")
1579
1626
  p = add("report", cmd_report, "the run report; --tokens = tokens and cost per step type")
@@ -1597,7 +1644,7 @@ def main(argv=None):
1597
1644
  print(f"autopilot {__version__}: {DESCRIPTION.split('. ')[0]}.")
1598
1645
  print(next_steps_text(Path.cwd()))
1599
1646
  return 0
1600
- if args.cmd in ("upgrade", "install"): # no project: nothing to find or remember
1647
+ if args.cmd in ("upgrade", "install", "setup", "uninstall"): # no project needed: nothing to find or remember
1601
1648
  return args.fn(args) or 0
1602
1649
  if args.path is None: # J2: no -C
1603
1650
  found = "." if args.cmd in NEW_PROJECT or (Path.cwd() / AGENT_DIR / "project.yaml").exists() else pick_project()
@@ -1607,7 +1654,13 @@ def main(argv=None):
1607
1654
  remember(Path(args.path).resolve())
1608
1655
  if getattr(args, "detach", False):
1609
1656
  return start_detached(Path(args.path).resolve(), [a for a in (argv or sys.argv[1:]) if a != "--detach"])
1610
- return args.fn(args) or 0
1657
+ rc = args.fn(args) or 0
1658
+ from .upgrade import auto_update # T2: after the command, once a day, never during a run
1659
+
1660
+ line = auto_update(args.cmd, Path(args.path).resolve())
1661
+ if line:
1662
+ print(line)
1663
+ return rc
1611
1664
 
1612
1665
 
1613
1666
  if __name__ == "__main__":