agent-toggle 0.13.1__tar.gz → 0.15.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 (93) hide show
  1. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/PKG-INFO +72 -51
  2. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/README.md +70 -50
  3. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/__init__.py +1 -1
  4. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/cli.py +12 -11
  5. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/help.py +7 -6
  6. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/locales/zh_tw_cli.py +3 -15
  7. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/locales/zh_tw_picker.py +12 -7
  8. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/shims/claude.md.tmpl +1 -1
  9. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/shims/generic.md.tmpl +1 -1
  10. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/menu.py +17 -1
  11. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/picker.py +240 -54
  12. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/theme.py +4 -3
  13. agent_toggle-0.15.0/agent_toggle/update_prompt.py +80 -0
  14. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle.egg-info/PKG-INFO +72 -51
  15. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle.egg-info/SOURCES.txt +0 -2
  16. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle.egg-info/requires.txt +1 -0
  17. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/pyproject.toml +1 -1
  18. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_cli.py +54 -95
  19. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_cli_surface.py +7 -1
  20. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_menu.py +9 -0
  21. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_mods.py +28 -17
  22. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_picker.py +294 -76
  23. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_same_name.py +2 -2
  24. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_theme.py +2 -2
  25. agent_toggle-0.15.0/tests/test_update_prompt.py +104 -0
  26. agent_toggle-0.13.1/agent_toggle/update_check.py +0 -361
  27. agent_toggle-0.13.1/agent_toggle/update_prompt.py +0 -242
  28. agent_toggle-0.13.1/tests/test_update_check.py +0 -423
  29. agent_toggle-0.13.1/tests/test_update_prompt.py +0 -90
  30. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/LICENSE +0 -0
  31. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/__main__.py +0 -0
  32. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/backends/__init__.py +0 -0
  33. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/backends/flag_json.py +0 -0
  34. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/backends/mcp_json.py +0 -0
  35. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/backends/mcp_toml.py +0 -0
  36. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/backends/plugin_cli.py +0 -0
  37. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/companions.py +0 -0
  38. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/completions.py +0 -0
  39. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/config.py +0 -0
  40. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/cost.py +0 -0
  41. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/doctor.py +0 -0
  42. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/fs.py +0 -0
  43. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/harnesses.py +0 -0
  44. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/i18n.py +0 -0
  45. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/locales/__init__.py +0 -0
  46. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/locales/zh_tw_common.py +0 -0
  47. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/locales/zh_tw_config.py +0 -0
  48. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/mechanisms.py +0 -0
  49. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ops.py +0 -0
  50. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/output.py +0 -0
  51. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/profiles.py +0 -0
  52. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/settings.py +0 -0
  53. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/spinner.py +0 -0
  54. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/store.py +0 -0
  55. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/toml_check.py +0 -0
  56. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/__init__.py +0 -0
  57. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/config_menu.py +0 -0
  58. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/logo.py +0 -0
  59. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/ui/model.py +0 -0
  60. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle/undo.py +0 -0
  61. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle.egg-info/dependency_links.txt +0 -0
  62. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle.egg-info/entry_points.txt +0 -0
  63. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/agent_toggle.egg-info/top_level.txt +0 -0
  64. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/setup.cfg +0 -0
  65. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_alias.py +0 -0
  66. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_all_type.py +0 -0
  67. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_backends.py +0 -0
  68. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_color.py +0 -0
  69. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_companions.py +0 -0
  70. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_completions.py +0 -0
  71. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_config.py +0 -0
  72. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_config_menu.py +0 -0
  73. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_conformance.py +0 -0
  74. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_containment.py +0 -0
  75. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_cost.py +0 -0
  76. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_crash.py +0 -0
  77. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_doctor.py +0 -0
  78. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_encoding.py +0 -0
  79. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_fs.py +0 -0
  80. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_harnesses.py +0 -0
  81. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_help.py +0 -0
  82. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_i18n.py +0 -0
  83. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_install.py +0 -0
  84. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_logo.py +0 -0
  85. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_mechanisms.py +0 -0
  86. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_platform.py +0 -0
  87. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_profiles.py +0 -0
  88. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_project.py +0 -0
  89. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_safety.py +0 -0
  90. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_secret_audit.py +0 -0
  91. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_settings.py +0 -0
  92. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_store.py +0 -0
  93. {agent_toggle-0.13.1 → agent_toggle-0.15.0}/tests/test_undo.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-toggle
3
- Version: 0.13.1
3
+ Version: 0.15.0
4
4
  Summary: Temporarily disable / re-enable AI-agent skills, agents, commands, rules, plugins and MCP servers across harnesses (Claude Code, Codex, Grok, OpenCode, OpenClaw, Copilot, Vibe, Devin, Antigravity)
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/weskao/agent-toggle
@@ -25,6 +25,7 @@ Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: telegram-kit<0.3,>=0.2.2
28
+ Requires-Dist: update-kit<0.2,>=0.1
28
29
  Provides-Extra: windows
29
30
  Requires-Dist: windows-curses; sys_platform == "win32" and extra == "windows"
30
31
  Provides-Extra: telegram
@@ -202,7 +203,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
202
203
  | `status` | health check: harnesses found, types each supports, parked counts, gitignore, untracked parked items, stale live twins, shared dirs, and state entries whose parked item is gone (a `stale` row with the fix command; read-only, still exit `0`) |
203
204
  | `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
204
205
  | `cost [--type T]` | estimated startup tokens per item, biggest first (read-only; `--harness H` filters; `--project <dir>` prices a repo's `.claude/` and `.mcp.json` instead of user scope) |
205
- | `update` | check PyPI now (ignores the cache and a skipped version) and print the commands that update the CLI, the Claude Code plugin (when installed) and the shims; runs none of them. `/agent-toggle update` in Claude Code does the same; opening the pane toasts a newer release once |
206
+ | `update` (also `--update`) | install the newest release tag (`uv tool install --force --no-sources git+<repo>@<tag>`, or `pipx install --force` for a pipx install), then, with the new version, update the Claude Code plugin (when installed) and the shims. Never asks. Exit `0` updated or already current, `1` the check or install failed (offline, rate limit), `2` a dev install (use `git pull`) or an install it cannot manage (the manual command is printed). Messages go to stderr; `--json` has no JSON for it. The `/agent-toggle` pane command has no `update`: run it in a terminal |
206
207
  | `install-shims` | write the skill shim into every installed harness that is on in the settings (an off one is skipped unless named with `--harness`); refuses to overwrite a file it did not write (`--dry-run` shows the plan); reports stale legacy `<dir>-disabled/` lines in a harness-home `.gitignore` (`--gitignore` removes them, `--no-gitignore` skips the check); on a terminal, then asks y/n for each problem `doctor` can fix (see [Fixing](#fixing)) |
207
208
  | `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
208
209
  | `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
@@ -316,7 +317,7 @@ agent-toggle # same, on a terminal; elsewhere prints help and exits
316
317
  ● delta codex 10 ▏ │ cost 0 now; ~300 tok if restored
317
318
  │ since 2026-09-19 10:00:00
318
319
  5 of 5 shown · 2/5
319
- Space toggle Enter apply Esc cancel ? help / search ←→ harness t type s sort p profile a all
320
+ Tab toggle Enter apply Esc cancel ? help / search ←→ harness t type s sort p profile a all
320
321
  ```
321
322
 
322
323
  What is on screen:
@@ -344,38 +345,54 @@ What is on screen:
344
345
 
345
346
  | key | action |
346
347
  |---|---|
347
- | `Space` | toggle the highlighted row (live / parked) and stay on it (while searching, the first `Space` separates search terms and a second one toggles) |
348
- | `Tab` | toggle the highlighted row and advance to the next |
348
+ | `Tab` | toggle the highlighted row (live / parked) and stay on it, including while searching |
349
+ | `Space` | enter a space in the search (separates search terms) |
349
350
  | `Enter` | apply every staged change (with `--dry-run`: show the plan) |
350
- | `Esc` / `Ctrl-C` | cancel; nothing is applied |
351
+ | `Esc` | leave; if changes are staged, review them in a centered Save changes? dialog |
352
+ | `Ctrl-C` | discard staged changes and leave |
351
353
  | `↑` `↓` `PgUp` `PgDn` `Home` `End`, `Ctrl-P` / `Ctrl-N` | move |
352
- | `/` | start a search; any other non-command letter starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name, the group (`mod` or `mods` finds every mod) and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
354
+ | `/` | start a search; any letter or digit starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name, the group (`mod` or `mods` finds every mod) and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
353
355
  | `Backspace` / `Ctrl-U` | delete one character / clear the filter |
354
- | `←` `→` | switch harness tab (also while typing a filter); `0` = All, `1`-`9` = that tab, `h` = next tab |
355
- | `t` | cycle the type filter |
356
- | `s` | cycle sort: name, cost (biggest first); applies within each type group |
357
- | `p` | profiles: stage a saved one, or save the live state (see below) |
356
+ | `←` `→` | switch harness tab (also while typing a filter) |
357
+ | `Ctrl-T` | cycle the type filter |
358
+ | `Ctrl-S` | cycle sort: name, cost (biggest first); applies within each type group |
359
+ | `Ctrl-O` | profiles: stage a saved one, or save the live state (see below) |
358
360
  | `?` | show the key list |
359
- | `a` / `Ctrl-A` | toggle every visible row |
360
- | `n` | toggle every row on this row's harness with the same name, of any type (skill, command, MCP, ...), shown or filtered out; like `disable all <name>` |
361
+ | `Ctrl-A` | toggle every visible row |
362
+ | `Ctrl-E` | toggle every row on this row's harness with the same name, of any type (skill, command, MCP, ...), shown or filtered out; like `disable all <name>` |
361
363
 
362
- The command keys (`t`, `s`, `p`, `h`, `a`, `n`, `0`-`9`, `?`) act only while no filter is active.
363
- Press `/` first to type a filter that begins with one of them; once the filter is non-empty,
364
- every letter just types.
364
+ Commands are `Ctrl` chords, so every letter and digit types into the search, and the chords
365
+ work while a filter is active too. `?` and `/` act only while no filter is active. The picker
366
+ reads keys in raw mode, so `Ctrl-S`, `Ctrl-T` and `Ctrl-O` reach it instead of the terminal
367
+ (flow control, `SIGINFO`, discard).
365
368
 
366
369
  The start view (sort, harness tab, type filter) comes from the `picker_sort`, `picker_harness`
367
370
  and `picker_type` [settings](#settings--config-menu). A profile skips harnesses that are hidden
368
371
  and counts those items as `hidden by settings`; `ui --harness <name>` shows that harness even
369
372
  when it is switched off.
370
373
 
371
- `p` opens a prompt over the list of saved profiles: type a number or name (or
374
+ `Ctrl-O` opens a prompt over the list of saved profiles: type a number or name (or
372
375
  `apply <name>`) and Enter to **stage** that profile's changes (only the items it
373
376
  mentions; ones this machine lacks are skipped), then Enter in the picker applies
374
377
  them like any other change, so `ui --dry-run` previews a profile and a stray
375
- `p` changes nothing. `save <name>` writes the live state (not your staged changes) as
378
+ `Ctrl-O` changes nothing. `save <name>` writes the live state (not your staged changes) as
376
379
  a profile, with the same name rules and project scope as `profile save`; it is
377
380
  off under `--dry-run`. Esc closes the prompt.
378
381
 
382
+ The exit dialog lists all staged changes, including rows hidden by a filter. Choose
383
+ **Yes** to apply and leave, or **No** to discard and leave. Use Left/Right
384
+ to select, Enter to confirm, Y/N shortcuts, or click either button (hovering selects it and underlines it). Esc in the
385
+ dialog returns to editing. Up/Down, PgUp/PgDn, Home/End and the mouse wheel scroll
386
+ a long change list. Its height is capped at 80% of the terminal (up to 24 lines,
387
+ with a compact layout on tiny terminals); the confirmation buttons stay visible.
388
+ Up/Down and the mouse wheel move the selected row, just like the main picker;
389
+ the list scrolls when the selection reaches its edge. Tab or a click toggles the
390
+ row's staged enabled/disabled state. The picker stays visible behind the opaque
391
+ dialog; both are painted together in one refresh to prevent background flashes.
392
+ Reverted rows stay in the review so they can be toggled again; Yes applies only
393
+ the remaining changes. The dialog follows the selected language and color mode;
394
+ with `--dry-run`, Yes only shows the plan.
395
+
379
396
  Nothing happens while the picker is open. Changes are staged, the screen is
380
397
  torn down, and only then do the real operations run, so their output (which
381
398
  companion files moved, which were kept because they are shared) is readable
@@ -390,7 +407,8 @@ numbers (`1 3 5-7`) to tick or untick, `n <number>` to toggle every type with th
390
407
  row's name, `/text` to filter (`/` alone clears it),
391
408
  `s` / `h` / `t` to sort and cycle the harness and type filters, `p` to list profiles,
392
409
  `p <number|name>` to stage one and `p save <name>` to save the live state, `a` to
393
- apply, `q` (or end of input) to cancel.
410
+ apply, `q` to review staged changes with a `y/n` save prompt (no changes: exit
411
+ immediately). End of input discards changes.
394
412
 
395
413
  ### Mods
396
414
 
@@ -408,7 +426,7 @@ plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothin
408
426
  grep -l modules ~/.claude/plugins/cache/*/*/*/hooks/hooks.json
409
427
  ```
410
428
 
411
- - The picker lists mods under **Mods**, right after Plugins. `t` cycles to it, the
429
+ - The picker lists mods under **Mods**, right after Plugins. `Ctrl-T` cycles to it, the
412
430
  `picker_type` setting can open on it, and a search for `mod` or `mods` finds every mod.
413
431
  The detail pane shows its type as `mod (plugin)`.
414
432
  - A mod is still a plugin everywhere else: it toggles through `claude plugin
@@ -470,33 +488,37 @@ invalid value in either is ignored with one warning on stderr.
470
488
  - `default_harness` is what `disable` / `enable` act on without `--harness`, even when that
471
489
  harness is switched off (with one warning on stderr);
472
490
  `disable` / `enable --project` without `--harness` still default to `claude`.
473
- - `language` translates the config menu, help, update prompt and picker chrome; command output stays English.
491
+ - `language` translates the config menu, help, update messages and picker chrome; command output stays English.
474
492
  - telegram-kit is a regular dependency now, so reinstalling keeps the Telegram settings working;
475
493
  `agent-toggle[telegram]` still resolves.
476
494
 
477
- ## Update check
478
-
479
- On every invocation (unless disabled) agent-toggle checks PyPI for a newer release. It starts
480
- before the command and is offered after it, on every exit path; it overlaps the command and waits at most 0.8 s afterwards.
481
-
482
- - **On a terminal** (stdin and stderr are TTYs, no `--json`): a panel on stderr, "agent-toggle X
483
- is available (you have Y)", with `Update now`, `Skip` and `Skip until next version`, a release
484
- notes link, and `↑` `↓` `Enter` `q`. `Update now` runs, in order and stopping at the first
485
- failure: `uv tool upgrade agent-toggle` (`pipx upgrade agent-toggle` for a pipx install), then,
486
- when the Claude Code plugin is installed, `claude plugin marketplace update agent-toggle` and
487
- `claude plugin update agent-toggle@agent-toggle`, then `agent-toggle install-shims`. A failure
488
- gives a yellow warning listing the commands to run yourself.
489
- - **Windows**: a running `agent-toggle.exe` cannot be replaced, so there is no `Update now`; the
490
- commands are printed instead (as below), to run once agent-toggle has exited.
491
- - **Off a terminal** (or `--json`): a stderr line, `agent-toggle X is available (you have Y)`,
492
- then those commands, one per line.
493
- - It never changes the exit code or stdout, so `--json` output stays one document.
494
-
495
- It is one `GET https://pypi.org/pypi/agent-toggle/json` on a background thread with a 0.8 s
496
- timeout, cached for 10 minutes in `~/.agent-toggle/update-check.json`. Nothing is sent beyond a
497
- normal HTTP request with the User-Agent `agent-toggle-update-check`. See `SECURITY.md`
498
- for the hardening. To turn it off, set `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting
499
- to off (the settings menu's "Check for updates").
495
+ ## Update
496
+
497
+ `agent-toggle update` (or `agent-toggle --update`) is a self-update through
498
+ update-kit (a small stdlib-only helper library): it asks the repository for its newest `vX.Y.Z`
499
+ tag (`git ls-remote --tags`), and when that is newer than the running version it installs it with
500
+ `uv tool install --force --no-sources git+<repo>@<tag>` (`pipx install --force` for a pipx install).
501
+ It then runs, with the new binary and in order: when the Claude Code plugin is installed,
502
+ `claude plugin marketplace update agent-toggle` and `claude plugin update agent-toggle@agent-toggle`,
503
+ then `agent-toggle install-shims`. A failing step prints a warning and makes the exit code `1`.
504
+ It never prompts and writes to stderr only.
505
+
506
+ | Exit | Meaning |
507
+ |---|---|
508
+ | `0` | updated, or already up to date |
509
+ | `1` | could not check (offline, rate limit) or the install failed |
510
+ | `2` | a dev install (`git pull` instead), or an install kind it cannot manage (the manual command is printed) |
511
+
512
+ On Windows a running `agent-toggle.exe` cannot be replaced, so the install runs detached after
513
+ agent-toggle exits (log in `%USERPROFILE%\.update-kit\`).
514
+
515
+ **The hint.** After a command, on an interactive terminal only (stderr is a TTY, never under
516
+ `--json`, pipes, launchd or timers), agent-toggle prints one stderr line when a newer release
517
+ exists, `agent-toggle vX.Y.Z is available (you have A) — run `agent-toggle update``. The check is
518
+ `git ls-remote` with a 1.5 s timeout, cached for 24 hours (failures too) in
519
+ `~/.update-kit/state.json`. It never changes the exit code or stdout. To turn it off, set
520
+ `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting to off (the settings menu's
521
+ "Check for updates").
500
522
 
501
523
  ## Help
502
524
 
@@ -888,7 +910,6 @@ bookkeeping.
888
910
  | `parked/<sha8>/` | items parked by `--project` (`<sha8>` = first 8 hex of the SHA-1 of the resolved project dir) |
889
911
  | `profiles/` | `<name>.json` profiles (dir `0700`, files `0600`) |
890
912
  | `config.json` | [settings](#settings--config-menu) and the Telegram chat id (mode `0600`, atomic write, unknown keys kept) |
891
- | `update-check.json` | the [update check](#update-check)'s 10-minute cache (mode `0600`, atomic write) |
892
913
 
893
914
  A killed run loses at most the one item in flight. If that was a flag write or a
894
915
  dir move (with its companions), its `pending` record (the full entry, a flag's
@@ -989,20 +1010,20 @@ claude --plugin-dir ./mod # then run /agent-toggle inside Claude
989
1010
  and the first and last list lines count the rows out of view (`↑ 4`, `↓ 12`).
990
1011
  - The mouse wheel over the list moves the cursor, as in the terminal picker; no click needed.
991
1012
  - Click the list once so it takes keys (Esc hands the keyboard back to the prompt). Then `↑` `↓`
992
- `PgUp` `PgDn` `Home` `End` (also `Ctrl-P` / `Ctrl-N`) move; `Space` or `Enter` toggles the row,
993
- `Tab` toggles and moves on; `/` or any other letter starts a search (terms ANDed over
994
- `type/name` and the group, letters-in-order if nothing matches; a second `Space` toggles);
995
- `s` sorts by name or cost; `n` toggles every type with this row's name (skill, command, MCP,
1013
+ `PgUp` `PgDn` `Home` `End` (also `Ctrl-P` / `Ctrl-N`) move; `Tab` toggles the row
1014
+ and stays on it, including while searching; `/` or any letter starts a search (terms ANDed over
1015
+ `type/name` and the group, letters-in-order if nothing matches; `Space` separates terms);
1016
+ `Ctrl-S` sorts by name or cost; `Ctrl-E` toggles every type with this row's name (skill, command, MCP,
996
1017
  ...), like `disable all <name>`; `Ctrl-U` clears the search. A click moves the cursor.
997
1018
  - A toggle applies at once (no staging, no Enter to apply); the toast names the target and the state
998
1019
  with an emoji and a bracketed tag (no colour codes, so nothing garbles; on the Linux console and the
999
1020
  legacy Windows console, where emoji draw as boxes, the tag alone): `✅ [ENABLED] skill: foo`, `⛔ [DISABLED] mod: bar (/reload-plugins)`,
1000
- `⛔ [DISABLED] rule: baz (takes effect in a new session)`, `⛔ [DISABLED] all: foo` after `n`, or `❌ [FAILED] disable skill: foo - <error>`.
1021
+ `⛔ [DISABLED] rule: baz (takes effect in a new session)`, `⛔ [DISABLED] all: foo` after `Ctrl-E`, or `❌ [FAILED] disable skill: foo - <error>`.
1001
1022
  The row flips the moment you press (and flips back
1002
1023
  if the CLI fails); the list reloads in the background. While a toggle runs, further keys are ignored.
1003
1024
  - Opening the pane shows the same braille spinner and `Loading resources…` as `agent-toggle ui`
1004
1025
  until the list arrives, and a spinner beside `working…` while a toggle runs.
1005
- - Not in the pane: harness tabs (claude only), and the `t` `p` `a` keys. Where Claude draws no
1026
+ - Not in the pane: harness tabs (claude only), and the `Ctrl-T` `Ctrl-O` `Ctrl-A` keys. Where Claude draws no
1006
1027
  such pane (the mobile app, VS Code) it is a plain list of buttons instead.
1007
1028
  - `/agent-toggle <command> ...` with `disable`, `enable`,
1008
1029
  `undo`, `list`, `status`, `cost` or `doctor` runs the CLI instead of opening the pane and prints
@@ -169,7 +169,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
169
169
  | `status` | health check: harnesses found, types each supports, parked counts, gitignore, untracked parked items, stale live twins, shared dirs, and state entries whose parked item is gone (a `stale` row with the fix command; read-only, still exit `0`) |
170
170
  | `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
171
171
  | `cost [--type T]` | estimated startup tokens per item, biggest first (read-only; `--harness H` filters; `--project <dir>` prices a repo's `.claude/` and `.mcp.json` instead of user scope) |
172
- | `update` | check PyPI now (ignores the cache and a skipped version) and print the commands that update the CLI, the Claude Code plugin (when installed) and the shims; runs none of them. `/agent-toggle update` in Claude Code does the same; opening the pane toasts a newer release once |
172
+ | `update` (also `--update`) | install the newest release tag (`uv tool install --force --no-sources git+<repo>@<tag>`, or `pipx install --force` for a pipx install), then, with the new version, update the Claude Code plugin (when installed) and the shims. Never asks. Exit `0` updated or already current, `1` the check or install failed (offline, rate limit), `2` a dev install (use `git pull`) or an install it cannot manage (the manual command is printed). Messages go to stderr; `--json` has no JSON for it. The `/agent-toggle` pane command has no `update`: run it in a terminal |
173
173
  | `install-shims` | write the skill shim into every installed harness that is on in the settings (an off one is skipped unless named with `--harness`); refuses to overwrite a file it did not write (`--dry-run` shows the plan); reports stale legacy `<dir>-disabled/` lines in a harness-home `.gitignore` (`--gitignore` removes them, `--no-gitignore` skips the check); on a terminal, then asks y/n for each problem `doctor` can fix (see [Fixing](#fixing)) |
174
174
  | `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
175
175
  | `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
@@ -283,7 +283,7 @@ agent-toggle # same, on a terminal; elsewhere prints help and exits
283
283
  ● delta codex 10 ▏ │ cost 0 now; ~300 tok if restored
284
284
  │ since 2026-09-19 10:00:00
285
285
  5 of 5 shown · 2/5
286
- Space toggle Enter apply Esc cancel ? help / search ←→ harness t type s sort p profile a all
286
+ Tab toggle Enter apply Esc cancel ? help / search ←→ harness t type s sort p profile a all
287
287
  ```
288
288
 
289
289
  What is on screen:
@@ -311,38 +311,54 @@ What is on screen:
311
311
 
312
312
  | key | action |
313
313
  |---|---|
314
- | `Space` | toggle the highlighted row (live / parked) and stay on it (while searching, the first `Space` separates search terms and a second one toggles) |
315
- | `Tab` | toggle the highlighted row and advance to the next |
314
+ | `Tab` | toggle the highlighted row (live / parked) and stay on it, including while searching |
315
+ | `Space` | enter a space in the search (separates search terms) |
316
316
  | `Enter` | apply every staged change (with `--dry-run`: show the plan) |
317
- | `Esc` / `Ctrl-C` | cancel; nothing is applied |
317
+ | `Esc` | leave; if changes are staged, review them in a centered Save changes? dialog |
318
+ | `Ctrl-C` | discard staged changes and leave |
318
319
  | `↑` `↓` `PgUp` `PgDn` `Home` `End`, `Ctrl-P` / `Ctrl-N` | move |
319
- | `/` | start a search; any other non-command letter starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name, the group (`mod` or `mods` finds every mod) and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
320
+ | `/` | start a search; any letter or digit starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name, the group (`mod` or `mods` finds every mod) and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
320
321
  | `Backspace` / `Ctrl-U` | delete one character / clear the filter |
321
- | `←` `→` | switch harness tab (also while typing a filter); `0` = All, `1`-`9` = that tab, `h` = next tab |
322
- | `t` | cycle the type filter |
323
- | `s` | cycle sort: name, cost (biggest first); applies within each type group |
324
- | `p` | profiles: stage a saved one, or save the live state (see below) |
322
+ | `←` `→` | switch harness tab (also while typing a filter) |
323
+ | `Ctrl-T` | cycle the type filter |
324
+ | `Ctrl-S` | cycle sort: name, cost (biggest first); applies within each type group |
325
+ | `Ctrl-O` | profiles: stage a saved one, or save the live state (see below) |
325
326
  | `?` | show the key list |
326
- | `a` / `Ctrl-A` | toggle every visible row |
327
- | `n` | toggle every row on this row's harness with the same name, of any type (skill, command, MCP, ...), shown or filtered out; like `disable all <name>` |
327
+ | `Ctrl-A` | toggle every visible row |
328
+ | `Ctrl-E` | toggle every row on this row's harness with the same name, of any type (skill, command, MCP, ...), shown or filtered out; like `disable all <name>` |
328
329
 
329
- The command keys (`t`, `s`, `p`, `h`, `a`, `n`, `0`-`9`, `?`) act only while no filter is active.
330
- Press `/` first to type a filter that begins with one of them; once the filter is non-empty,
331
- every letter just types.
330
+ Commands are `Ctrl` chords, so every letter and digit types into the search, and the chords
331
+ work while a filter is active too. `?` and `/` act only while no filter is active. The picker
332
+ reads keys in raw mode, so `Ctrl-S`, `Ctrl-T` and `Ctrl-O` reach it instead of the terminal
333
+ (flow control, `SIGINFO`, discard).
332
334
 
333
335
  The start view (sort, harness tab, type filter) comes from the `picker_sort`, `picker_harness`
334
336
  and `picker_type` [settings](#settings--config-menu). A profile skips harnesses that are hidden
335
337
  and counts those items as `hidden by settings`; `ui --harness <name>` shows that harness even
336
338
  when it is switched off.
337
339
 
338
- `p` opens a prompt over the list of saved profiles: type a number or name (or
340
+ `Ctrl-O` opens a prompt over the list of saved profiles: type a number or name (or
339
341
  `apply <name>`) and Enter to **stage** that profile's changes (only the items it
340
342
  mentions; ones this machine lacks are skipped), then Enter in the picker applies
341
343
  them like any other change, so `ui --dry-run` previews a profile and a stray
342
- `p` changes nothing. `save <name>` writes the live state (not your staged changes) as
344
+ `Ctrl-O` changes nothing. `save <name>` writes the live state (not your staged changes) as
343
345
  a profile, with the same name rules and project scope as `profile save`; it is
344
346
  off under `--dry-run`. Esc closes the prompt.
345
347
 
348
+ The exit dialog lists all staged changes, including rows hidden by a filter. Choose
349
+ **Yes** to apply and leave, or **No** to discard and leave. Use Left/Right
350
+ to select, Enter to confirm, Y/N shortcuts, or click either button (hovering selects it and underlines it). Esc in the
351
+ dialog returns to editing. Up/Down, PgUp/PgDn, Home/End and the mouse wheel scroll
352
+ a long change list. Its height is capped at 80% of the terminal (up to 24 lines,
353
+ with a compact layout on tiny terminals); the confirmation buttons stay visible.
354
+ Up/Down and the mouse wheel move the selected row, just like the main picker;
355
+ the list scrolls when the selection reaches its edge. Tab or a click toggles the
356
+ row's staged enabled/disabled state. The picker stays visible behind the opaque
357
+ dialog; both are painted together in one refresh to prevent background flashes.
358
+ Reverted rows stay in the review so they can be toggled again; Yes applies only
359
+ the remaining changes. The dialog follows the selected language and color mode;
360
+ with `--dry-run`, Yes only shows the plan.
361
+
346
362
  Nothing happens while the picker is open. Changes are staged, the screen is
347
363
  torn down, and only then do the real operations run, so their output (which
348
364
  companion files moved, which were kept because they are shared) is readable
@@ -357,7 +373,8 @@ numbers (`1 3 5-7`) to tick or untick, `n <number>` to toggle every type with th
357
373
  row's name, `/text` to filter (`/` alone clears it),
358
374
  `s` / `h` / `t` to sort and cycle the harness and type filters, `p` to list profiles,
359
375
  `p <number|name>` to stage one and `p save <name>` to save the live state, `a` to
360
- apply, `q` (or end of input) to cancel.
376
+ apply, `q` to review staged changes with a `y/n` save prompt (no changes: exit
377
+ immediately). End of input discards changes.
361
378
 
362
379
  ### Mods
363
380
 
@@ -375,7 +392,7 @@ plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothin
375
392
  grep -l modules ~/.claude/plugins/cache/*/*/*/hooks/hooks.json
376
393
  ```
377
394
 
378
- - The picker lists mods under **Mods**, right after Plugins. `t` cycles to it, the
395
+ - The picker lists mods under **Mods**, right after Plugins. `Ctrl-T` cycles to it, the
379
396
  `picker_type` setting can open on it, and a search for `mod` or `mods` finds every mod.
380
397
  The detail pane shows its type as `mod (plugin)`.
381
398
  - A mod is still a plugin everywhere else: it toggles through `claude plugin
@@ -437,33 +454,37 @@ invalid value in either is ignored with one warning on stderr.
437
454
  - `default_harness` is what `disable` / `enable` act on without `--harness`, even when that
438
455
  harness is switched off (with one warning on stderr);
439
456
  `disable` / `enable --project` without `--harness` still default to `claude`.
440
- - `language` translates the config menu, help, update prompt and picker chrome; command output stays English.
457
+ - `language` translates the config menu, help, update messages and picker chrome; command output stays English.
441
458
  - telegram-kit is a regular dependency now, so reinstalling keeps the Telegram settings working;
442
459
  `agent-toggle[telegram]` still resolves.
443
460
 
444
- ## Update check
445
-
446
- On every invocation (unless disabled) agent-toggle checks PyPI for a newer release. It starts
447
- before the command and is offered after it, on every exit path; it overlaps the command and waits at most 0.8 s afterwards.
448
-
449
- - **On a terminal** (stdin and stderr are TTYs, no `--json`): a panel on stderr, "agent-toggle X
450
- is available (you have Y)", with `Update now`, `Skip` and `Skip until next version`, a release
451
- notes link, and `↑` `↓` `Enter` `q`. `Update now` runs, in order and stopping at the first
452
- failure: `uv tool upgrade agent-toggle` (`pipx upgrade agent-toggle` for a pipx install), then,
453
- when the Claude Code plugin is installed, `claude plugin marketplace update agent-toggle` and
454
- `claude plugin update agent-toggle@agent-toggle`, then `agent-toggle install-shims`. A failure
455
- gives a yellow warning listing the commands to run yourself.
456
- - **Windows**: a running `agent-toggle.exe` cannot be replaced, so there is no `Update now`; the
457
- commands are printed instead (as below), to run once agent-toggle has exited.
458
- - **Off a terminal** (or `--json`): a stderr line, `agent-toggle X is available (you have Y)`,
459
- then those commands, one per line.
460
- - It never changes the exit code or stdout, so `--json` output stays one document.
461
-
462
- It is one `GET https://pypi.org/pypi/agent-toggle/json` on a background thread with a 0.8 s
463
- timeout, cached for 10 minutes in `~/.agent-toggle/update-check.json`. Nothing is sent beyond a
464
- normal HTTP request with the User-Agent `agent-toggle-update-check`. See `SECURITY.md`
465
- for the hardening. To turn it off, set `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting
466
- to off (the settings menu's "Check for updates").
461
+ ## Update
462
+
463
+ `agent-toggle update` (or `agent-toggle --update`) is a self-update through
464
+ update-kit (a small stdlib-only helper library): it asks the repository for its newest `vX.Y.Z`
465
+ tag (`git ls-remote --tags`), and when that is newer than the running version it installs it with
466
+ `uv tool install --force --no-sources git+<repo>@<tag>` (`pipx install --force` for a pipx install).
467
+ It then runs, with the new binary and in order: when the Claude Code plugin is installed,
468
+ `claude plugin marketplace update agent-toggle` and `claude plugin update agent-toggle@agent-toggle`,
469
+ then `agent-toggle install-shims`. A failing step prints a warning and makes the exit code `1`.
470
+ It never prompts and writes to stderr only.
471
+
472
+ | Exit | Meaning |
473
+ |---|---|
474
+ | `0` | updated, or already up to date |
475
+ | `1` | could not check (offline, rate limit) or the install failed |
476
+ | `2` | a dev install (`git pull` instead), or an install kind it cannot manage (the manual command is printed) |
477
+
478
+ On Windows a running `agent-toggle.exe` cannot be replaced, so the install runs detached after
479
+ agent-toggle exits (log in `%USERPROFILE%\.update-kit\`).
480
+
481
+ **The hint.** After a command, on an interactive terminal only (stderr is a TTY, never under
482
+ `--json`, pipes, launchd or timers), agent-toggle prints one stderr line when a newer release
483
+ exists, `agent-toggle vX.Y.Z is available (you have A) — run `agent-toggle update``. The check is
484
+ `git ls-remote` with a 1.5 s timeout, cached for 24 hours (failures too) in
485
+ `~/.update-kit/state.json`. It never changes the exit code or stdout. To turn it off, set
486
+ `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting to off (the settings menu's
487
+ "Check for updates").
467
488
 
468
489
  ## Help
469
490
 
@@ -855,7 +876,6 @@ bookkeeping.
855
876
  | `parked/<sha8>/` | items parked by `--project` (`<sha8>` = first 8 hex of the SHA-1 of the resolved project dir) |
856
877
  | `profiles/` | `<name>.json` profiles (dir `0700`, files `0600`) |
857
878
  | `config.json` | [settings](#settings--config-menu) and the Telegram chat id (mode `0600`, atomic write, unknown keys kept) |
858
- | `update-check.json` | the [update check](#update-check)'s 10-minute cache (mode `0600`, atomic write) |
859
879
 
860
880
  A killed run loses at most the one item in flight. If that was a flag write or a
861
881
  dir move (with its companions), its `pending` record (the full entry, a flag's
@@ -956,20 +976,20 @@ claude --plugin-dir ./mod # then run /agent-toggle inside Claude
956
976
  and the first and last list lines count the rows out of view (`↑ 4`, `↓ 12`).
957
977
  - The mouse wheel over the list moves the cursor, as in the terminal picker; no click needed.
958
978
  - Click the list once so it takes keys (Esc hands the keyboard back to the prompt). Then `↑` `↓`
959
- `PgUp` `PgDn` `Home` `End` (also `Ctrl-P` / `Ctrl-N`) move; `Space` or `Enter` toggles the row,
960
- `Tab` toggles and moves on; `/` or any other letter starts a search (terms ANDed over
961
- `type/name` and the group, letters-in-order if nothing matches; a second `Space` toggles);
962
- `s` sorts by name or cost; `n` toggles every type with this row's name (skill, command, MCP,
979
+ `PgUp` `PgDn` `Home` `End` (also `Ctrl-P` / `Ctrl-N`) move; `Tab` toggles the row
980
+ and stays on it, including while searching; `/` or any letter starts a search (terms ANDed over
981
+ `type/name` and the group, letters-in-order if nothing matches; `Space` separates terms);
982
+ `Ctrl-S` sorts by name or cost; `Ctrl-E` toggles every type with this row's name (skill, command, MCP,
963
983
  ...), like `disable all <name>`; `Ctrl-U` clears the search. A click moves the cursor.
964
984
  - A toggle applies at once (no staging, no Enter to apply); the toast names the target and the state
965
985
  with an emoji and a bracketed tag (no colour codes, so nothing garbles; on the Linux console and the
966
986
  legacy Windows console, where emoji draw as boxes, the tag alone): `✅ [ENABLED] skill: foo`, `⛔ [DISABLED] mod: bar (/reload-plugins)`,
967
- `⛔ [DISABLED] rule: baz (takes effect in a new session)`, `⛔ [DISABLED] all: foo` after `n`, or `❌ [FAILED] disable skill: foo - <error>`.
987
+ `⛔ [DISABLED] rule: baz (takes effect in a new session)`, `⛔ [DISABLED] all: foo` after `Ctrl-E`, or `❌ [FAILED] disable skill: foo - <error>`.
968
988
  The row flips the moment you press (and flips back
969
989
  if the CLI fails); the list reloads in the background. While a toggle runs, further keys are ignored.
970
990
  - Opening the pane shows the same braille spinner and `Loading resources…` as `agent-toggle ui`
971
991
  until the list arrives, and a spinner beside `working…` while a toggle runs.
972
- - Not in the pane: harness tabs (claude only), and the `t` `p` `a` keys. Where Claude draws no
992
+ - Not in the pane: harness tabs (claude only), and the `Ctrl-T` `Ctrl-O` `Ctrl-A` keys. Where Claude draws no
973
993
  such pane (the mobile app, VS Code) it is a plain list of buttons instead.
974
994
  - `/agent-toggle <command> ...` with `disable`, `enable`,
975
995
  `undo`, `list`, `status`, `cost` or `doctor` runs the CLI instead of opening the pane and prints
@@ -1,3 +1,3 @@
1
1
  """agent-toggle: temporarily disable / re-enable AI-agent resources."""
2
2
 
3
- __version__ = "0.13.1"
3
+ __version__ = "0.15.0"
@@ -36,7 +36,7 @@ Usage:
36
36
  failure, 2 usage error, 3 locked, 4 unsupported pair / harness missing.
37
37
  Settings: $HOME/.agent-toggle/config.json (see `config`); env overrides
38
38
  AGENT_TOGGLE_UPDATE_CHECK / _COLOR / _LANG / _DEFAULT_HARNESS.
39
- Update check: on by default, stderr only; AGENT_TOGGLE_UPDATE_CHECK=0 turns it off.
39
+ Update hint: on by default, a terminal's stderr only; AGENT_TOGGLE_UPDATE_CHECK=0 turns it off.
40
40
  """
41
41
  from __future__ import annotations
42
42
 
@@ -695,7 +695,7 @@ def build_parser() -> argparse.ArgumentParser:
695
695
  sub.add_parser("doctor", parents=[common],
696
696
  help="check harness layouts and state against disk; offers y/n fixes")
697
697
  sub.add_parser("update", parents=[common],
698
- help="check for a newer release; show the commands that update everything")
698
+ help="install the newest release, then refresh the plugin and shims")
699
699
  cf = sub.add_parser("config", parents=[common],
700
700
  help="Telegram settings for the CI failure alerts")
701
701
  cf.add_argument("action", nargs="?", choices=("test", "sync-ci"),
@@ -723,9 +723,9 @@ def build_parser() -> argparse.ArgumentParser:
723
723
  def main(argv: list[str] | None = None) -> int:
724
724
  """Run one command and return its exit code (the process exit code).
725
725
 
726
- Settings apply first; the update check starts before dispatch and is offered in a
727
- `finally`, so every way out (a command, --help/--version, a usage error, Locked,
728
- CliError) gets it. It writes to stderr only and never changes the exit code."""
726
+ Settings apply first; the update hint is printed in a `finally`, so every way out
727
+ (a command, --help/--version, a usage error, Locked, CliError) gets it. It writes
728
+ to stderr only and never changes the exit code."""
729
729
  argv = normalize_argv(sys.argv[1:] if argv is None else argv)
730
730
  try:
731
731
  language, color = settings.get("language"), settings.get("color")
@@ -734,16 +734,17 @@ def main(argv: list[str] | None = None) -> int:
734
734
  i18n.set_language(language)
735
735
  color = scan_color(argv, color) # --color > env > setting > auto
736
736
  i = _command_index(argv)
737
- # `update` is the check; `completion` may run on every shell start (eval "$(...)")
738
- started = None if argv[i:i + 1] in (["update"], ["completion"]) else update_prompt.start()
737
+ hint = True
739
738
  try:
740
739
  return _main(argv, color)
741
740
  except KeyboardInterrupt:
742
- started = None # never prompt after the user hit Ctrl-C
741
+ hint = False # never ask anything after the user hit Ctrl-C
743
742
  raise
744
743
  finally:
745
- with contextlib.suppress(KeyboardInterrupt): # Ctrl-C while waiting on the check
746
- update_prompt.offer(started, json_mode="--json" in argv, color_mode=color)
744
+ # `update` is the update; `completion` may run on every shell start (eval "$(...)")
745
+ if hint and argv[i:i + 1] not in (["update"], ["completion"]):
746
+ with contextlib.suppress(KeyboardInterrupt):
747
+ update_prompt.hint(json_mode="--json" in argv)
747
748
 
748
749
 
749
750
  def _main(argv: list[str], color: str) -> int:
@@ -804,7 +805,7 @@ def _main(argv: list[str], color: str) -> int:
804
805
  if fixes and _interactive(out):
805
806
  doctor.offer_fixes(fixes, out)
806
807
  elif cmd == "update":
807
- update_prompt.cmd_update(out)
808
+ return update_prompt.cmd_update()
808
809
  elif cmd == "config":
809
810
  config.cmd_config(args, out)
810
811
  elif cmd == "completion":
@@ -137,12 +137,13 @@ def commands(default_harness: str = "claude") -> dict[str, Cmd]:
137
137
  examples=["config", "config test", "config sync-ci --dry-run"]),
138
138
  "update": Cmd(
139
139
  "update",
140
- t("help.update.summary", "check for a newer release; show how to update"),
141
- t("help.update.desc", "Ask PyPI now (ignoring the cache and a skipped version) and, "
142
- "when a newer release exists, print the commands that update "
143
- "the CLI, the Claude Code plugin (if installed) and the shims. "
144
- "Runs none of them."),
145
- examples=["update", "update --json"]),
140
+ t("help.update.summary", "install the newest release; refresh the plugin and shims"),
141
+ t("help.update.desc", "Install the newest release tag (uv tool or pipx), then, with the "
142
+ "new version, update the Claude Code plugin (if installed) and "
143
+ "reinstall the shims. Never asks; `--update` is the same. A dev "
144
+ "install says to git pull (exit 2). Exit 1 when the check or "
145
+ "the install fails."),
146
+ examples=["update"]),
146
147
  "completion": Cmd(
147
148
  "completion bash|zsh|fish|powershell",
148
149
  t("help.completion.summary", "print the shell completion script"),
@@ -27,9 +27,9 @@ CATALOG: dict[str, str] = {
27
27
  "help.cost.summary": "每個項目的預估啟動 token 數,由大到小",
28
28
  "help.cost.desc": "估算每個項目啟動時載入多少 token(字元數/4,±25%),"
29
29
  "以及暫存項目已省下多少。",
30
- "help.update.summary": "檢查新版本;顯示更新方式",
31
- "help.update.desc": "立即向 PyPI 查詢(忽略快取與已略過的版本);有新版本時,印出更新 CLI、"
32
- "Claude Code 外掛(若已安裝)與 shim 的指令。不會執行任何指令。",
30
+ "help.update.summary": "安裝最新版本,並更新外掛與 shim",
31
+ "help.update.desc": "安裝最新的發行標籤(uv tool / pipx),完成後以新版更新 Claude Code 外掛(若已安裝)"
32
+ "並重新安裝 shim。不會提問;`--update` 等同 `update`。開發版安裝請改用 git pull(結束碼 2)。",
33
33
  "help.completion.summary": "印出 shell 自動補完腳本",
34
34
  "help.completion.desc": "在 stdout 印出某個 shell 的 Tab 補完腳本;"
35
35
  "存到該 shell 載入補完的位置(見 README「Shell completion」)。",
@@ -85,16 +85,4 @@ CATALOG: dict[str, str] = {
85
85
  "help.opt.help": "顯示說明(`help <command>` 看單一指令)",
86
86
  "help.arguments": "參數",
87
87
  "help.options": "選項",
88
- # update prompt
89
- "update.available": "agent-toggle %s 已推出(目前版本 %s)",
90
- "update.keys": "↑↓ 選擇 · ⏎ 確認 · q/Ctrl+C 略過",
91
- "update.keys_ascii": "上/下 選擇,Enter 確認,q/Ctrl+C 略過",
92
- "update.choose": "請選擇 1-3(Enter = 1,q/Ctrl+C = 略過):",
93
- "update.now": "立即更新",
94
- "update.skip": "略過",
95
- "update.skip_detail": "下次執行再問",
96
- "update.skip_version": "略過到下一個版本",
97
- "update.skip_version_detail": "有更新的版本時再問",
98
- "update.notes": "版本說明:%s",
99
- "update.failed": "更新未完成 — 請自行執行:%s",
100
88
  }