aicp-cli 0.7.0__tar.gz → 0.8.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 (55) hide show
  1. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/CHANGELOG.md +14 -0
  2. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/PKG-INFO +100 -45
  3. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/README.md +99 -44
  4. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/pyproject.toml +1 -1
  5. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/cli.py +10 -2
  6. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/menu.py +9 -5
  7. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/uv.lock +2 -2
  8. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/.github/workflows/ci.yml +0 -0
  9. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/.github/workflows/release.yml +0 -0
  10. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/.gitignore +0 -0
  11. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/CLAUDE.md +0 -0
  12. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/LICENSE +0 -0
  13. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/TODO.md +0 -0
  14. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/config.example.json +0 -0
  15. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/docs/images/aicp-run.png +0 -0
  16. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/skills/commit.md +0 -0
  17. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/skills/safe-git-push/SKILL.md +0 -0
  18. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/skills/safe-git-push/scripts/safe_push.py +0 -0
  19. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/__init__.py +0 -0
  20. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/_keyreader.py +0 -0
  21. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/_utils.py +0 -0
  22. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/agentcfg.py +0 -0
  23. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/agents.json +0 -0
  24. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/agents.py +0 -0
  25. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/budget.py +0 -0
  26. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/config.py +0 -0
  27. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/contracts.py +0 -0
  28. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/gitflow.py +0 -0
  29. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/i18n.py +0 -0
  30. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/notify.py +0 -0
  31. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/present.py +0 -0
  32. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/quota.py +0 -0
  33. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/runner.py +0 -0
  34. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/secrets.py +0 -0
  35. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/skills.py +0 -0
  36. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/src/aicp/timing.py +0 -0
  37. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/conftest.py +0 -0
  38. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_agentcfg.py +0 -0
  39. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_agents.py +0 -0
  40. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_budget.py +0 -0
  41. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_cli.py +0 -0
  42. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_config.py +0 -0
  43. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_cross_platform.py +0 -0
  44. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_gitflow.py +0 -0
  45. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_i18n.py +0 -0
  46. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_menu.py +0 -0
  47. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_menu_skills.py +0 -0
  48. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_notify.py +0 -0
  49. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_present.py +0 -0
  50. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_quota.py +0 -0
  51. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_runner.py +0 -0
  52. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_secrets.py +0 -0
  53. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_skills.py +0 -0
  54. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_timing.py +0 -0
  55. {aicp_cli-0.7.0 → aicp_cli-0.8.0}/tests/test_undo.py +0 -0
@@ -1,3 +1,13 @@
1
+ ## [0.8.0] - 2026-09-18
2
+
3
+ ### 🐛 Bug Fixes
4
+
5
+ - **agents:** Keep agent listing widths stable across toggles
6
+ - **cli:** Parenthesize concatenated f-string for ruff ISC004
7
+
8
+ ### 📚 Documentation
9
+
10
+ - **readme:** Document configurable agent chain and skills field
1
11
  ## [0.7.0] - 2026-09-17
2
12
 
3
13
  ### 🚀 Features
@@ -7,6 +17,10 @@
7
17
  ### 🐛 Bug Fixes
8
18
 
9
19
  - **menu:** Size panels to their own content
20
+
21
+ ### 📚 Documentation
22
+
23
+ - **changelog:** Release v0.7.0
10
24
  ## [0.6.0] - 2026-09-17
11
25
 
12
26
  ### 📚 Documentation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: aicp-cli
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: AI commit + push, with a git-verified result summary
5
5
  Project-URL: Repository, https://github.com/weskao/aicp
6
6
  Project-URL: Issues, https://github.com/weskao/aicp/issues
@@ -14,11 +14,19 @@ Description-Content-Type: text/markdown
14
14
 
15
15
  AI commit + push, with a git-verified result summary.
16
16
 
17
- `aicp` runs an AI coding CLI to write your commit message(s) and push, trying
18
- a fallback chain of six CLIs — `copilot` → `agy` → `codex` → `claude` →
19
- `vibe` → `grok` — until one exits 0; anything not installed is skipped. It
17
+ `aicp` runs an AI coding CLI to write your commit message(s) and push, walking
18
+ a fallback chain until one exits 0; anything not installed is skipped. It
20
19
  sends that CLI two literal prompts, `/commit` then `/safe-git-push`.
21
20
 
21
+ **The chain is yours to arrange, and it isn't a fixed set.** The built-in
22
+ registry ships six CLIs — `copilot`, `agy`, `codex`, `claude`, `vibe`,
23
+ `grok` — but that's only the default *order*, not a hardcoded pipeline. Move
24
+ any one of them to the front with `aicp --swap-ai`, set the whole order from
25
+ `aicp --config` or `AICP_CLI_ORDER`, and add your own CLIs (or drop built-in
26
+ ones) with `aicp --agents`. Every agent in the resolved registry is a
27
+ first-class link in the chain — there is no privileged first CLI and no cap
28
+ on how many you run with.
29
+
22
30
  The part that matters: **aicp never trusts the CLI's own account of what
23
31
  happened.** An AI CLI can print "pushed!" and exit 0 while `/safe-git-push`
24
32
  quietly aborted inside it. So every number in the result summary — new
@@ -28,17 +36,36 @@ failed `git fetch` is never read as "already in sync" either: a stale
28
36
  remote-tracking ref resolves just fine and would otherwise report a clean
29
37
  push that never reached the remote.
30
38
 
31
- [Install](#install) · [Set up skills](#set-up-skills) · [Run it](#run-it) ·
32
- [`--undo`](#--undo) · [Automation / CI](#automation--ci) ·
33
- [Configuration](#configuration) · [Custom agents](#custom-agents) · [Safety](#safety) ·
34
- [Platform support](#platform-support)
39
+ ## Contents
40
+
41
+ **Getting started**
42
+
43
+ - [Requirements](#requirements)
44
+ - [Install](#install)
45
+ - [Set up skills](#set-up-skills)
46
+ - [Run it](#run-it)
47
+
48
+ **Using it**
49
+
50
+ - [Fallback order](#fallback-order) — reorder the chain, `--swap-ai`
51
+ - [Fallback results and quota limits](#fallback-results-and-quota-limits)
52
+ - [`--undo`](#--undo) — take back the last commit
53
+ - [Automation / CI](#automation--ci) — the non-interactive flags
54
+
55
+ **Reference**
56
+
57
+ - [Configuration](#configuration) — every setting and env var
58
+ - [Custom agents](#custom-agents) — add your own AI CLI
59
+ - [Safety](#safety) — the pre-commit secret scan
60
+ - [Platform support](#platform-support) — macOS, Linux, Windows
35
61
 
36
62
  ## Requirements
37
63
 
38
64
  - Python 3.10 or newer
39
65
  - [`uv`](https://docs.astral.sh/uv/) — the install path below
40
66
  - `git`
41
- - At least one of the six AI CLIs above on `PATH`
67
+ - At least one AI CLI from the chain on `PATH` (a built-in one, or your own
68
+ via [Custom agents](#custom-agents))
42
69
 
43
70
  The Python package itself has no runtime dependencies.
44
71
 
@@ -79,44 +106,26 @@ Targets follow each CLI's own config directory, not its binary name — `agy`
79
106
  | CLI | Config dir | `/commit` installed? | `/safe-git-push` installed? |
80
107
  | --- | --- | --- | --- |
81
108
  | `claude` | `~/.claude` | No — keeps your existing `commands/commit.md` | Yes |
82
- | `codex` | `~/.codex` | No — keeps your existing `/commit` | Yes |
83
- | `copilot` | `~/.copilot` | No — keeps your existing `/commit` | Yes |
84
- | `agy` (Gemini CLI) | `~/.gemini` | No — keeps your existing `/commit` | Yes |
85
- | `vibe` | `~/.vibe` | No — keeps your existing `/commit` | Yes |
86
- | `grok` | `$GROK_HOME` when set, otherwise `~/.grok` | No — keeps your existing `/commit` | Yes |
87
-
88
- `/commit` is never installed: aicp keeps each CLI's existing command or skill
89
- definition. Claude resolves that definition from `commands/commit.md`; the
90
- other CLIs keep their existing `/commit` as well. Every configured CLI gets
91
- `/safe-git-push`.
109
+ | `codex` | `~/.codex` | Yes | Yes |
110
+ | `copilot` | `~/.copilot` | Yes | Yes |
111
+ | `agy` (Gemini CLI) | `~/.gemini` | Yes | Yes |
112
+ | `vibe` | `~/.vibe` | Yes | Yes |
113
+ | `grok` | `$GROK_HOME` when set, otherwise `~/.grok` | Yes | Yes |
114
+
115
+ Every configured CLI gets `/safe-git-push`. `/commit` is installed everywhere
116
+ *except* `claude`, which already resolves `/commit` from
117
+ `commands/commit.md` — a second definition under `skills/` would compete with
118
+ it. Which skills an agent gets is data, not a branch: it's the `skills` field
119
+ of that agent's registry entry, so a [custom agent](#custom-agents) declares
120
+ its own list the same way.
121
+
122
+ The vendored text is rewritten per CLI on install — the shipped copy talks
123
+ about `.claude/` and `CLAUDE.md`, which mean nothing to `codex` or `agy`, so
124
+ each install substitutes that agent's own `config_dir` name and `memory_file`.
92
125
 
93
126
  A CLI whose config directory doesn't exist at all is skipped, never created —
94
127
  aicp only ever installs into a CLI you've actually set up.
95
128
 
96
- ### Fallback results and quota limits
97
-
98
- The opening run panel prints the resolved chain, and the commit panel and
99
- final RESULT table name the CLI that handled each step (`—` when skipped).
100
- When a CLI emits a supported, exact quota/rate-limit signal, aicp records the
101
- outcome as `quota`, notifies through the usual notification path, and excludes
102
- that CLI from the rest of that one commit/push flow. The exclusion also
103
- persists: the CLI stays skipped for `AICP_QUOTA_COOLDOWN` seconds (default one
104
- hour, state in `~/.aicp/quota.json`), because a token or rate-limit wall
105
- normally stands for hours and every run inside that window would otherwise burn
106
- a full budget per step on a CLI that cannot succeed. A skipped CLI prints how
107
- long is left; `AICP_QUOTA_COOLDOWN=0` switches the cooldown off entirely, and
108
- deleting the file clears it.
109
-
110
- Only the `quota` outcome starts a cooldown. A timeout does not — a CLI that
111
- hangs on its rate limit instead of exiting is indistinguishable from one merely
112
- running long, and sidelining it for an hour on that guess costs more than the
113
- retry does.
114
-
115
- Exact detection is intentionally narrow. It is supported for Codex, Claude,
116
- Vibe, and Grok only; Copilot and `agy` have no verified quota signature, so a
117
- nonzero exit from either remains an ordinary failure and is still eligible for
118
- the next step.
119
-
120
129
  ## Run it
121
130
 
122
131
  The whole command surface is two things to remember:
@@ -141,6 +150,52 @@ same as `aicp --config`.
141
150
  right now": which steps run, message language, fallback CLI order, the
142
151
  skills install/upgrade view, and a health check.
143
152
 
153
+ ### Fallback order
154
+
155
+ The order is a saved preference, not a property of the tool. Three ways to
156
+ set it, all writing the same `aicp_cli_order` key:
157
+
158
+ ```sh
159
+ aicp --swap-ai # pick a CLI, it trades places with whoever holds #1
160
+ aicp --config # the AI CLI order row, arrow keys
161
+ ```
162
+
163
+ ```sh
164
+ # ~/.aicp/config.json — or AICP_CLI_ORDER="claude codex" aicp for one run
165
+ { "aicp_cli_order": "claude codex copilot agy vibe grok" }
166
+ ```
167
+
168
+ `--swap-ai` is a rotation, not a destructive set: the CLI you pick moves to
169
+ `#1` and the old `#1` takes its place, so nothing falls out of the chain. It
170
+ lists every agent in the registry, including ones whose binary isn't on
171
+ `PATH` — a missing binary must not hide a choice you're about to install.
172
+ Naming only the first few is enough; everything you leave out is appended
173
+ behind them in registry order.
174
+
175
+ ### Fallback results and quota limits
176
+
177
+ The opening run panel prints the resolved chain, and the commit panel and
178
+ final RESULT table name the CLI that handled each step (`—` when skipped).
179
+ When a CLI emits a supported, exact quota/rate-limit signal, aicp records the
180
+ outcome as `quota`, notifies through the usual notification path, and excludes
181
+ that CLI from the rest of that one commit/push flow. The exclusion also
182
+ persists: the CLI stays skipped for `AICP_QUOTA_COOLDOWN` seconds (default one
183
+ hour, state in `~/.aicp/quota.json`), because a token or rate-limit wall
184
+ normally stands for hours and every run inside that window would otherwise burn
185
+ a full budget per step on a CLI that cannot succeed. A skipped CLI prints how
186
+ long is left; `AICP_QUOTA_COOLDOWN=0` switches the cooldown off entirely, and
187
+ deleting the file clears it.
188
+
189
+ Only the `quota` outcome starts a cooldown. A timeout does not — a CLI that
190
+ hangs on its rate limit instead of exiting is indistinguishable from one merely
191
+ running long, and sidelining it for an hour on that guess costs more than the
192
+ retry does.
193
+
194
+ Exact detection is intentionally narrow. It is supported for Codex, Claude,
195
+ Vibe, and Grok only; Copilot, `agy` and any agent you add yourself have no
196
+ verified quota signature, so a nonzero exit from those remains an ordinary
197
+ failure and is still eligible for the next step.
198
+
144
199
  ### `--undo`
145
200
 
146
201
  ```sh
@@ -223,7 +278,7 @@ integration tests and installed-wheel checks on all three operating systems.
223
278
  | `config_dir` | Absolute path or `~/`-relative directory; home is resolved when used. |
224
279
  | `config_dir_env` | Optional environment variable overriding that directory (currently `GROK_HOME`). |
225
280
  | `skills_dir` | Relative directory inside `config_dir` where skills are installed. |
226
- | `skills` | Vendored skills to install: `/safe-git-push`. Every CLI keeps its existing `/commit`. |
281
+ | `skills` | Which vendored skills to install for this agent — any of `safe-git-push`, `commit`. Omitting `commit` is how `claude` keeps its own `commands/commit.md`. |
227
282
  | `memory_file` | Instruction filename used when adapting vendored skill text. Project directory references use the basename of `config_dir`. |
228
283
  | `args` | Argument array containing exactly one standalone `{prompt}`, replaced with the complete prompt as one argument. No shell evaluation. |
229
284
 
@@ -351,7 +406,7 @@ See [`config.example.json`](config.example.json) for a ready-to-copy template.
351
406
  | `AICP_DO_COMMIT` | `1` | Run the `/commit` step. `0` = only push what's already committed. |
352
407
  | `AICP_DO_PUSH` | `1` | Run the `/safe-git-push` step. `0` = commit and stop. |
353
408
  | `AICP_LANG` | `en` | Message language: `en` or `zh-TW`, everywhere including notifications. |
354
- | `AICP_CLI_ORDER` | `copilot agy codex claude vibe grok` | Fallback order. A prefix is enough — any roster name left out is appended after it, in roster order. An unknown or repeated name is refused outright and the default order is used. |
409
+ | `AICP_CLI_ORDER` | registry order (`copilot agy codex claude vibe grok` out of the box) | Fallback order, space-separated. A prefix is enough — any agent left out is appended after it, in registry order, so adding an agent never invalidates an order you already saved. An unknown or repeated name is refused outright and the default order is used. |
355
410
  | `AICP_TZ` | `Asia/Taipei` | IANA zone name used to render commit timestamps. Anything else falls back to the default. |
356
411
  | `AICP_TZ_LABEL` | `UTC+8` | Cosmetic label shown beside those timestamps; not validated. |
357
412
  | `AICP_STEP_TIMEOUT` | *(unset)* | Pins every CLI's per-step budget in seconds, skipping the formula and history below entirely. |
@@ -2,11 +2,19 @@
2
2
 
3
3
  AI commit + push, with a git-verified result summary.
4
4
 
5
- `aicp` runs an AI coding CLI to write your commit message(s) and push, trying
6
- a fallback chain of six CLIs — `copilot` → `agy` → `codex` → `claude` →
7
- `vibe` → `grok` — until one exits 0; anything not installed is skipped. It
5
+ `aicp` runs an AI coding CLI to write your commit message(s) and push, walking
6
+ a fallback chain until one exits 0; anything not installed is skipped. It
8
7
  sends that CLI two literal prompts, `/commit` then `/safe-git-push`.
9
8
 
9
+ **The chain is yours to arrange, and it isn't a fixed set.** The built-in
10
+ registry ships six CLIs — `copilot`, `agy`, `codex`, `claude`, `vibe`,
11
+ `grok` — but that's only the default *order*, not a hardcoded pipeline. Move
12
+ any one of them to the front with `aicp --swap-ai`, set the whole order from
13
+ `aicp --config` or `AICP_CLI_ORDER`, and add your own CLIs (or drop built-in
14
+ ones) with `aicp --agents`. Every agent in the resolved registry is a
15
+ first-class link in the chain — there is no privileged first CLI and no cap
16
+ on how many you run with.
17
+
10
18
  The part that matters: **aicp never trusts the CLI's own account of what
11
19
  happened.** An AI CLI can print "pushed!" and exit 0 while `/safe-git-push`
12
20
  quietly aborted inside it. So every number in the result summary — new
@@ -16,17 +24,36 @@ failed `git fetch` is never read as "already in sync" either: a stale
16
24
  remote-tracking ref resolves just fine and would otherwise report a clean
17
25
  push that never reached the remote.
18
26
 
19
- [Install](#install) · [Set up skills](#set-up-skills) · [Run it](#run-it) ·
20
- [`--undo`](#--undo) · [Automation / CI](#automation--ci) ·
21
- [Configuration](#configuration) · [Custom agents](#custom-agents) · [Safety](#safety) ·
22
- [Platform support](#platform-support)
27
+ ## Contents
28
+
29
+ **Getting started**
30
+
31
+ - [Requirements](#requirements)
32
+ - [Install](#install)
33
+ - [Set up skills](#set-up-skills)
34
+ - [Run it](#run-it)
35
+
36
+ **Using it**
37
+
38
+ - [Fallback order](#fallback-order) — reorder the chain, `--swap-ai`
39
+ - [Fallback results and quota limits](#fallback-results-and-quota-limits)
40
+ - [`--undo`](#--undo) — take back the last commit
41
+ - [Automation / CI](#automation--ci) — the non-interactive flags
42
+
43
+ **Reference**
44
+
45
+ - [Configuration](#configuration) — every setting and env var
46
+ - [Custom agents](#custom-agents) — add your own AI CLI
47
+ - [Safety](#safety) — the pre-commit secret scan
48
+ - [Platform support](#platform-support) — macOS, Linux, Windows
23
49
 
24
50
  ## Requirements
25
51
 
26
52
  - Python 3.10 or newer
27
53
  - [`uv`](https://docs.astral.sh/uv/) — the install path below
28
54
  - `git`
29
- - At least one of the six AI CLIs above on `PATH`
55
+ - At least one AI CLI from the chain on `PATH` (a built-in one, or your own
56
+ via [Custom agents](#custom-agents))
30
57
 
31
58
  The Python package itself has no runtime dependencies.
32
59
 
@@ -67,44 +94,26 @@ Targets follow each CLI's own config directory, not its binary name — `agy`
67
94
  | CLI | Config dir | `/commit` installed? | `/safe-git-push` installed? |
68
95
  | --- | --- | --- | --- |
69
96
  | `claude` | `~/.claude` | No — keeps your existing `commands/commit.md` | Yes |
70
- | `codex` | `~/.codex` | No — keeps your existing `/commit` | Yes |
71
- | `copilot` | `~/.copilot` | No — keeps your existing `/commit` | Yes |
72
- | `agy` (Gemini CLI) | `~/.gemini` | No — keeps your existing `/commit` | Yes |
73
- | `vibe` | `~/.vibe` | No — keeps your existing `/commit` | Yes |
74
- | `grok` | `$GROK_HOME` when set, otherwise `~/.grok` | No — keeps your existing `/commit` | Yes |
75
-
76
- `/commit` is never installed: aicp keeps each CLI's existing command or skill
77
- definition. Claude resolves that definition from `commands/commit.md`; the
78
- other CLIs keep their existing `/commit` as well. Every configured CLI gets
79
- `/safe-git-push`.
97
+ | `codex` | `~/.codex` | Yes | Yes |
98
+ | `copilot` | `~/.copilot` | Yes | Yes |
99
+ | `agy` (Gemini CLI) | `~/.gemini` | Yes | Yes |
100
+ | `vibe` | `~/.vibe` | Yes | Yes |
101
+ | `grok` | `$GROK_HOME` when set, otherwise `~/.grok` | Yes | Yes |
102
+
103
+ Every configured CLI gets `/safe-git-push`. `/commit` is installed everywhere
104
+ *except* `claude`, which already resolves `/commit` from
105
+ `commands/commit.md` — a second definition under `skills/` would compete with
106
+ it. Which skills an agent gets is data, not a branch: it's the `skills` field
107
+ of that agent's registry entry, so a [custom agent](#custom-agents) declares
108
+ its own list the same way.
109
+
110
+ The vendored text is rewritten per CLI on install — the shipped copy talks
111
+ about `.claude/` and `CLAUDE.md`, which mean nothing to `codex` or `agy`, so
112
+ each install substitutes that agent's own `config_dir` name and `memory_file`.
80
113
 
81
114
  A CLI whose config directory doesn't exist at all is skipped, never created —
82
115
  aicp only ever installs into a CLI you've actually set up.
83
116
 
84
- ### Fallback results and quota limits
85
-
86
- The opening run panel prints the resolved chain, and the commit panel and
87
- final RESULT table name the CLI that handled each step (`—` when skipped).
88
- When a CLI emits a supported, exact quota/rate-limit signal, aicp records the
89
- outcome as `quota`, notifies through the usual notification path, and excludes
90
- that CLI from the rest of that one commit/push flow. The exclusion also
91
- persists: the CLI stays skipped for `AICP_QUOTA_COOLDOWN` seconds (default one
92
- hour, state in `~/.aicp/quota.json`), because a token or rate-limit wall
93
- normally stands for hours and every run inside that window would otherwise burn
94
- a full budget per step on a CLI that cannot succeed. A skipped CLI prints how
95
- long is left; `AICP_QUOTA_COOLDOWN=0` switches the cooldown off entirely, and
96
- deleting the file clears it.
97
-
98
- Only the `quota` outcome starts a cooldown. A timeout does not — a CLI that
99
- hangs on its rate limit instead of exiting is indistinguishable from one merely
100
- running long, and sidelining it for an hour on that guess costs more than the
101
- retry does.
102
-
103
- Exact detection is intentionally narrow. It is supported for Codex, Claude,
104
- Vibe, and Grok only; Copilot and `agy` have no verified quota signature, so a
105
- nonzero exit from either remains an ordinary failure and is still eligible for
106
- the next step.
107
-
108
117
  ## Run it
109
118
 
110
119
  The whole command surface is two things to remember:
@@ -129,6 +138,52 @@ same as `aicp --config`.
129
138
  right now": which steps run, message language, fallback CLI order, the
130
139
  skills install/upgrade view, and a health check.
131
140
 
141
+ ### Fallback order
142
+
143
+ The order is a saved preference, not a property of the tool. Three ways to
144
+ set it, all writing the same `aicp_cli_order` key:
145
+
146
+ ```sh
147
+ aicp --swap-ai # pick a CLI, it trades places with whoever holds #1
148
+ aicp --config # the AI CLI order row, arrow keys
149
+ ```
150
+
151
+ ```sh
152
+ # ~/.aicp/config.json — or AICP_CLI_ORDER="claude codex" aicp for one run
153
+ { "aicp_cli_order": "claude codex copilot agy vibe grok" }
154
+ ```
155
+
156
+ `--swap-ai` is a rotation, not a destructive set: the CLI you pick moves to
157
+ `#1` and the old `#1` takes its place, so nothing falls out of the chain. It
158
+ lists every agent in the registry, including ones whose binary isn't on
159
+ `PATH` — a missing binary must not hide a choice you're about to install.
160
+ Naming only the first few is enough; everything you leave out is appended
161
+ behind them in registry order.
162
+
163
+ ### Fallback results and quota limits
164
+
165
+ The opening run panel prints the resolved chain, and the commit panel and
166
+ final RESULT table name the CLI that handled each step (`—` when skipped).
167
+ When a CLI emits a supported, exact quota/rate-limit signal, aicp records the
168
+ outcome as `quota`, notifies through the usual notification path, and excludes
169
+ that CLI from the rest of that one commit/push flow. The exclusion also
170
+ persists: the CLI stays skipped for `AICP_QUOTA_COOLDOWN` seconds (default one
171
+ hour, state in `~/.aicp/quota.json`), because a token or rate-limit wall
172
+ normally stands for hours and every run inside that window would otherwise burn
173
+ a full budget per step on a CLI that cannot succeed. A skipped CLI prints how
174
+ long is left; `AICP_QUOTA_COOLDOWN=0` switches the cooldown off entirely, and
175
+ deleting the file clears it.
176
+
177
+ Only the `quota` outcome starts a cooldown. A timeout does not — a CLI that
178
+ hangs on its rate limit instead of exiting is indistinguishable from one merely
179
+ running long, and sidelining it for an hour on that guess costs more than the
180
+ retry does.
181
+
182
+ Exact detection is intentionally narrow. It is supported for Codex, Claude,
183
+ Vibe, and Grok only; Copilot, `agy` and any agent you add yourself have no
184
+ verified quota signature, so a nonzero exit from those remains an ordinary
185
+ failure and is still eligible for the next step.
186
+
132
187
  ### `--undo`
133
188
 
134
189
  ```sh
@@ -211,7 +266,7 @@ integration tests and installed-wheel checks on all three operating systems.
211
266
  | `config_dir` | Absolute path or `~/`-relative directory; home is resolved when used. |
212
267
  | `config_dir_env` | Optional environment variable overriding that directory (currently `GROK_HOME`). |
213
268
  | `skills_dir` | Relative directory inside `config_dir` where skills are installed. |
214
- | `skills` | Vendored skills to install: `/safe-git-push`. Every CLI keeps its existing `/commit`. |
269
+ | `skills` | Which vendored skills to install for this agent — any of `safe-git-push`, `commit`. Omitting `commit` is how `claude` keeps its own `commands/commit.md`. |
215
270
  | `memory_file` | Instruction filename used when adapting vendored skill text. Project directory references use the basename of `config_dir`. |
216
271
  | `args` | Argument array containing exactly one standalone `{prompt}`, replaced with the complete prompt as one argument. No shell evaluation. |
217
272
 
@@ -339,7 +394,7 @@ See [`config.example.json`](config.example.json) for a ready-to-copy template.
339
394
  | `AICP_DO_COMMIT` | `1` | Run the `/commit` step. `0` = only push what's already committed. |
340
395
  | `AICP_DO_PUSH` | `1` | Run the `/safe-git-push` step. `0` = commit and stop. |
341
396
  | `AICP_LANG` | `en` | Message language: `en` or `zh-TW`, everywhere including notifications. |
342
- | `AICP_CLI_ORDER` | `copilot agy codex claude vibe grok` | Fallback order. A prefix is enough — any roster name left out is appended after it, in roster order. An unknown or repeated name is refused outright and the default order is used. |
397
+ | `AICP_CLI_ORDER` | registry order (`copilot agy codex claude vibe grok` out of the box) | Fallback order, space-separated. A prefix is enough — any agent left out is appended after it, in registry order, so adding an agent never invalidates an order you already saved. An unknown or repeated name is refused outright and the default order is used. |
343
398
  | `AICP_TZ` | `Asia/Taipei` | IANA zone name used to render commit timestamps. Anything else falls back to the default. |
344
399
  | `AICP_TZ_LABEL` | `UTC+8` | Cosmetic label shown beside those timestamps; not validated. |
345
400
  | `AICP_STEP_TIMEOUT` | *(unset)* | Pins every CLI's per-step budget in seconds, skipping the formula and history below entirely. |
@@ -2,7 +2,7 @@
2
2
  # PyPI distribution name only — the `aicp` name was taken in 2021 by an
3
3
  # unrelated project. The command, import path and repo stay `aicp`.
4
4
  name = "aicp-cli"
5
- version = "0.7.0"
5
+ version = "0.8.0"
6
6
  description = "AI commit + push, with a git-verified result summary"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.10"
@@ -305,9 +305,17 @@ def _doctor(*, as_json: bool) -> int:
305
305
 
306
306
 
307
307
  def _agents_list() -> int:
308
+ inventory = agents.inventory()
309
+ exec_w = max((present.width(row.executable) for row in inventory), default=0)
308
310
  rows = [
309
- (row.name, f"{row.executable} {DIM}· {t(*agentcfg.STATE_LABELS[row.state])}{RESET}")
310
- for row in agents.inventory()
311
+ (
312
+ row.name,
313
+ (
314
+ f"{row.executable}{' ' * (exec_w - present.width(row.executable))}"
315
+ f" {DIM}· {t(*agentcfg.STATE_LABELS[row.state])}{RESET}"
316
+ ),
317
+ )
318
+ for row in inventory
311
319
  ]
312
320
  _echo(present.render(rows, t("agents_title", "AGENTS")))
313
321
  _dim(t("agents_hint", " aicp --agents set <name> executable=… · disable <name> · reset <name>"))
@@ -442,13 +442,16 @@ def _agents_lines(state: MenuState, selected: int, message: str | None = None) -
442
442
  than read off a printed list afterwards. Sized to its own content (a
443
443
  short roster, not the terminal width) and stable across keypresses: the
444
444
  row text never changes length as the cursor moves, only which one is
445
- bold. The executable column is padded to its own widest entry so the
446
- state label starts in the same place on every row — the fixed-width-
447
- column trick :func:`render_table` gets for free and a two-column
448
- ``(label, value)`` panel does not.
445
+ bold. The executable column is padded to its own widest entry, and the
446
+ state label to the widest of all *possible* states (not just the ones
447
+ currently in play) — so toggling a row's state can never widen or narrow
448
+ the panel that toggle is being watched in. The fixed-width-column trick
449
+ :func:`render_table` gets for free and a two-column ``(label, value)``
450
+ panel does not.
449
451
  """
450
452
  rows = _agent_rows(state)
451
453
  exec_w = max((width(row.executable) for row in rows), default=0)
454
+ state_w = max(width(_t(state.lang, *msg)) for msg in agentcfg.STATE_LABELS.values())
452
455
  body: list[tuple[str, str]] = []
453
456
  for i, row in enumerate(rows, start=1):
454
457
  marker = "›" if selected == i else " "
@@ -458,7 +461,8 @@ def _agents_lines(state: MenuState, selected: int, message: str | None = None) -
458
461
  label = f"{RESET}{BOLD}{label}{RESET}"
459
462
  state_label = _t(state.lang, *agentcfg.STATE_LABELS[row.state])
460
463
  pad = " " * (exec_w - width(row.executable))
461
- value = f"{color}{mark}{RESET} {row.executable}{pad} {DIM}{state_label}{RESET}"
464
+ state_pad = " " * (state_w - width(state_label))
465
+ value = f"{color}{mark}{RESET} {row.executable}{pad} {DIM}{state_label}{state_pad}{RESET}"
462
466
  body.append((label, value))
463
467
  notes = [message] if message else []
464
468
  notes.append(
@@ -4,7 +4,7 @@ requires-python = ">=3.10"
4
4
 
5
5
  [[package]]
6
6
  name = "aicp-cli"
7
- version = "0.7.0"
7
+ version = "0.8.0"
8
8
  source = { editable = "." }
9
9
 
10
10
  [package.dev-dependencies]
@@ -49,7 +49,7 @@ name = "exceptiongroup"
49
49
  version = "1.3.1"
50
50
  source = { registry = "https://pypi.org/simple" }
51
51
  dependencies = [
52
- { name = "typing-extensions" },
52
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
53
53
  ]
54
54
  sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
55
55
  wheels = [
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes