agent-toggle 0.11.4__tar.gz → 0.12.1__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 (90) hide show
  1. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/PKG-INFO +17 -15
  2. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/README.md +15 -14
  3. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/__init__.py +1 -1
  4. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/backends/mcp_json.py +1 -1
  5. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/cli.py +1 -1
  6. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/config.py +4 -4
  7. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/cost.py +1 -1
  8. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/doctor.py +1 -1
  9. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/fs.py +9 -0
  10. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/harnesses.py +8 -3
  11. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/locales/zh_tw_config.py +1 -1
  12. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/mechanisms.py +5 -4
  13. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/config_menu.py +2 -2
  14. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle.egg-info/PKG-INFO +17 -15
  15. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle.egg-info/requires.txt +1 -0
  16. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/pyproject.toml +3 -1
  17. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_backends.py +6 -0
  18. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_config.py +2 -2
  19. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_config_menu.py +3 -3
  20. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_cost.py +10 -0
  21. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_harnesses.py +6 -1
  22. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/LICENSE +0 -0
  23. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/__main__.py +0 -0
  24. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/backends/__init__.py +0 -0
  25. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/backends/flag_json.py +0 -0
  26. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/backends/mcp_toml.py +0 -0
  27. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/backends/plugin_cli.py +0 -0
  28. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/companions.py +0 -0
  29. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/help.py +0 -0
  30. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/i18n.py +0 -0
  31. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/locales/__init__.py +0 -0
  32. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/locales/zh_tw_cli.py +0 -0
  33. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/locales/zh_tw_common.py +0 -0
  34. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/locales/zh_tw_picker.py +0 -0
  35. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ops.py +0 -0
  36. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/output.py +0 -0
  37. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/profiles.py +0 -0
  38. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/settings.py +0 -0
  39. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/shims/claude.md.tmpl +0 -0
  40. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/shims/generic.md.tmpl +0 -0
  41. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/spinner.py +0 -0
  42. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/store.py +0 -0
  43. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/toml_check.py +0 -0
  44. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/__init__.py +0 -0
  45. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/logo.py +0 -0
  46. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/menu.py +0 -0
  47. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/model.py +0 -0
  48. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/picker.py +0 -0
  49. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/ui/theme.py +0 -0
  50. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/undo.py +0 -0
  51. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/update_check.py +0 -0
  52. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle/update_prompt.py +0 -0
  53. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle.egg-info/SOURCES.txt +0 -0
  54. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle.egg-info/dependency_links.txt +0 -0
  55. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle.egg-info/entry_points.txt +0 -0
  56. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/agent_toggle.egg-info/top_level.txt +0 -0
  57. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/setup.cfg +0 -0
  58. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_alias.py +0 -0
  59. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_all_type.py +0 -0
  60. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_cli.py +0 -0
  61. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_cli_surface.py +0 -0
  62. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_color.py +0 -0
  63. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_companions.py +0 -0
  64. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_completions.py +0 -0
  65. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_conformance.py +0 -0
  66. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_containment.py +0 -0
  67. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_crash.py +0 -0
  68. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_doctor.py +0 -0
  69. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_encoding.py +0 -0
  70. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_fs.py +0 -0
  71. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_help.py +0 -0
  72. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_i18n.py +0 -0
  73. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_install.py +0 -0
  74. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_logo.py +0 -0
  75. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_mechanisms.py +0 -0
  76. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_menu.py +0 -0
  77. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_mods.py +0 -0
  78. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_picker.py +0 -0
  79. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_platform.py +0 -0
  80. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_profiles.py +0 -0
  81. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_project.py +0 -0
  82. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_safety.py +0 -0
  83. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_same_name.py +0 -0
  84. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_secret_audit.py +0 -0
  85. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_settings.py +0 -0
  86. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_store.py +0 -0
  87. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_theme.py +0 -0
  88. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_undo.py +0 -0
  89. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_update_check.py +0 -0
  90. {agent_toggle-0.11.4 → agent_toggle-0.12.1}/tests/test_update_prompt.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-toggle
3
- Version: 0.11.4
3
+ Version: 0.12.1
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
@@ -24,6 +24,7 @@ Classifier: Topic :: Utilities
24
24
  Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
+ Requires-Dist: telegram-kit<0.3,>=0.2.2
27
28
  Provides-Extra: windows
28
29
  Requires-Dist: windows-curses; sys_platform == "win32" and extra == "windows"
29
30
  Provides-Extra: telegram
@@ -142,21 +143,20 @@ exits `1`. A shim written by a version before the marker counts as foreign;
142
143
  delete it and re-run. `--dry-run` shows the same plan, refusals included. The
143
144
  tool itself stays in one place.
144
145
 
145
- **Optional: Telegram alerts for CI** (for maintainers of a fork or clone). Install
146
- with the `telegram` extra, which adds [telegram-kit](https://pypi.org/project/telegram-kit/)
147
- (itself stdlib-only), then run the setup once; see [CI notifications](#ci-notifications):
146
+ **Optional: Telegram alerts for CI** (for maintainers of a fork or clone). The
147
+ Telegram support comes from [telegram-kit](https://pypi.org/project/telegram-kit/)
148
+ (itself stdlib-only), which every install includes, so a reinstall or upgrade keeps
149
+ your settings. Run the setup once; see [CI notifications](#ci-notifications):
148
150
 
149
151
  ```sh
150
- uv tool install "agent-toggle[telegram]" # or: pipx install "agent-toggle[telegram]"
151
- pip install -e ".[telegram]" # from a checkout
152
152
  agent-toggle config # settings menu: bot token + chat id live under Notifications
153
153
  agent-toggle config sync-ci # set the two GitHub repository secrets
154
154
  ```
155
155
 
156
- Without the extra everything else works exactly as before. Only `config test`,
157
- `config sync-ci` (both exit `4` without it) and the Telegram rows of the
158
- [settings menu](#settings--config-menu) need it; bare `config` still opens, and
159
- those rows read `install agent-toggle[telegram]`.
156
+ The old `agent-toggle[telegram]` extra still resolves and is now a no-op. If
157
+ telegram-kit is missing from a broken install, `config test` and `config sync-ci`
158
+ exit `4`, bare `config` still opens, and the Telegram rows read
159
+ `telegram-kit missing: reinstall agent-toggle`.
160
160
 
161
161
  **Optional: the Claude Code pane mod** (the picker as a pane inside Claude Code, claude harness
162
162
  only). It is not part of the PyPI package; with `agent-toggle` already on `PATH` from one of the
@@ -204,7 +204,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
204
204
  | `undo` | reverse the last logged batch (`--dry-run` shows the plan) |
205
205
  | `profile save\|apply\|diff\|list\|delete\|rename` | named sets of live items; see [Profiles](#profiles) |
206
206
  | `config` | settings menu (curses, else a numbered list); `config --json` lists every setting and the package version; see [Settings & config menu](#settings--config-menu) |
207
- | `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; needs the `[telegram]` extra; see [CI notifications](#ci-notifications) |
207
+ | `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; see [CI notifications](#ci-notifications) |
208
208
  | `help [command]` | styled help; `help ui` = `--help ui` = `ui --help`; see [Help](#help) |
209
209
  | `doctor` | check each harness layout and `state.json` against disk; exit `1` only on an `error` row; on a terminal, asks y/n per fix it can run itself |
210
210
  | `migrate` | import an older `~/.claude-toggle/` state, and move legacy `<home>/<dir>-disabled/` park dirs into `~/.agent-toggle/parked/user/` (see [Upgrading the park layout](#upgrading-the-park-layout)); safe to re-run |
@@ -391,6 +391,8 @@ a band above the prompt, buttons) and change Claude Code's behaviour. A plugin w
391
391
  `hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
392
392
  plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
393
393
 
394
+ ![Claude Mods: toggling mods from the picker](https://raw.githubusercontent.com/weskao/agent-toggle/main/docs/images/claude-mods-demo.gif)
395
+
394
396
  - The check reads the install path `claude plugin list` reports and every cached version
395
397
  under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
396
398
  To list the installed mods by hand:
@@ -463,8 +465,8 @@ invalid value in either is ignored with one warning on stderr.
463
465
  harness is switched off (with one warning on stderr);
464
466
  `disable` / `enable --project` without `--harness` still default to `claude`.
465
467
  - `language` translates the config menu, help, update prompt and picker chrome; command output stays English.
466
- - Bare `config` works without the `[telegram]` extra; `config test` and `config sync-ci` are
467
- unchanged and need it (exit `4` without it).
468
+ - telegram-kit is a regular dependency now, so reinstalling keeps the Telegram settings working;
469
+ `agent-toggle[telegram]` still resolves.
468
470
 
469
471
  ## Update check
470
472
 
@@ -523,7 +525,7 @@ logo, centred, when the window is tall enough to fit the whole menu beside it; t
523
525
  | copilot | `~/.copilot` | ✓ | ✓ | — | — | — | ✓ `mcp-config.json` |
524
526
  | vibe | `~/.vibe` | ✓ | — | — | — | — | — |
525
527
  | devin | `~/.devin` | — | — | — | — | — | — |
526
- | agy | `~/.antigravity` | — | — | — | — | — | — |
528
+ | agy | `~/.gemini/config` | ✓ | — | — | — | — | ✓ `mcp_config.json` |
527
529
 
528
530
  Per-harness locations, mechanisms and the harness versions this was checked
529
531
  against are in [docs/harnesses.md](docs/harnesses.md).
@@ -1041,7 +1043,7 @@ agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on
1041
1043
 
1042
1044
  In the [settings menu](#settings--config-menu) an inline field commits on `Enter`, cancels on
1043
1045
  `Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
1044
- secrets (the same as `config test` / `config sync-ci`, and they need the extra). The bot token
1046
+ secrets (the same as `config test` / `config sync-ci`). The bot token
1045
1047
  is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
1046
1048
  through telegram-kit, never in a file or on a command line, and is shown masked. With no
1047
1049
  credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
@@ -110,21 +110,20 @@ exits `1`. A shim written by a version before the marker counts as foreign;
110
110
  delete it and re-run. `--dry-run` shows the same plan, refusals included. The
111
111
  tool itself stays in one place.
112
112
 
113
- **Optional: Telegram alerts for CI** (for maintainers of a fork or clone). Install
114
- with the `telegram` extra, which adds [telegram-kit](https://pypi.org/project/telegram-kit/)
115
- (itself stdlib-only), then run the setup once; see [CI notifications](#ci-notifications):
113
+ **Optional: Telegram alerts for CI** (for maintainers of a fork or clone). The
114
+ Telegram support comes from [telegram-kit](https://pypi.org/project/telegram-kit/)
115
+ (itself stdlib-only), which every install includes, so a reinstall or upgrade keeps
116
+ your settings. Run the setup once; see [CI notifications](#ci-notifications):
116
117
 
117
118
  ```sh
118
- uv tool install "agent-toggle[telegram]" # or: pipx install "agent-toggle[telegram]"
119
- pip install -e ".[telegram]" # from a checkout
120
119
  agent-toggle config # settings menu: bot token + chat id live under Notifications
121
120
  agent-toggle config sync-ci # set the two GitHub repository secrets
122
121
  ```
123
122
 
124
- Without the extra everything else works exactly as before. Only `config test`,
125
- `config sync-ci` (both exit `4` without it) and the Telegram rows of the
126
- [settings menu](#settings--config-menu) need it; bare `config` still opens, and
127
- those rows read `install agent-toggle[telegram]`.
123
+ The old `agent-toggle[telegram]` extra still resolves and is now a no-op. If
124
+ telegram-kit is missing from a broken install, `config test` and `config sync-ci`
125
+ exit `4`, bare `config` still opens, and the Telegram rows read
126
+ `telegram-kit missing: reinstall agent-toggle`.
128
127
 
129
128
  **Optional: the Claude Code pane mod** (the picker as a pane inside Claude Code, claude harness
130
129
  only). It is not part of the PyPI package; with `agent-toggle` already on `PATH` from one of the
@@ -172,7 +171,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
172
171
  | `undo` | reverse the last logged batch (`--dry-run` shows the plan) |
173
172
  | `profile save\|apply\|diff\|list\|delete\|rename` | named sets of live items; see [Profiles](#profiles) |
174
173
  | `config` | settings menu (curses, else a numbered list); `config --json` lists every setting and the package version; see [Settings & config menu](#settings--config-menu) |
175
- | `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; needs the `[telegram]` extra; see [CI notifications](#ci-notifications) |
174
+ | `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; see [CI notifications](#ci-notifications) |
176
175
  | `help [command]` | styled help; `help ui` = `--help ui` = `ui --help`; see [Help](#help) |
177
176
  | `doctor` | check each harness layout and `state.json` against disk; exit `1` only on an `error` row; on a terminal, asks y/n per fix it can run itself |
178
177
  | `migrate` | import an older `~/.claude-toggle/` state, and move legacy `<home>/<dir>-disabled/` park dirs into `~/.agent-toggle/parked/user/` (see [Upgrading the park layout](#upgrading-the-park-layout)); safe to re-run |
@@ -359,6 +358,8 @@ a band above the prompt, buttons) and change Claude Code's behaviour. A plugin w
359
358
  `hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
360
359
  plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
361
360
 
361
+ ![Claude Mods: toggling mods from the picker](https://raw.githubusercontent.com/weskao/agent-toggle/main/docs/images/claude-mods-demo.gif)
362
+
362
363
  - The check reads the install path `claude plugin list` reports and every cached version
363
364
  under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
364
365
  To list the installed mods by hand:
@@ -431,8 +432,8 @@ invalid value in either is ignored with one warning on stderr.
431
432
  harness is switched off (with one warning on stderr);
432
433
  `disable` / `enable --project` without `--harness` still default to `claude`.
433
434
  - `language` translates the config menu, help, update prompt and picker chrome; command output stays English.
434
- - Bare `config` works without the `[telegram]` extra; `config test` and `config sync-ci` are
435
- unchanged and need it (exit `4` without it).
435
+ - telegram-kit is a regular dependency now, so reinstalling keeps the Telegram settings working;
436
+ `agent-toggle[telegram]` still resolves.
436
437
 
437
438
  ## Update check
438
439
 
@@ -491,7 +492,7 @@ logo, centred, when the window is tall enough to fit the whole menu beside it; t
491
492
  | copilot | `~/.copilot` | ✓ | ✓ | — | — | — | ✓ `mcp-config.json` |
492
493
  | vibe | `~/.vibe` | ✓ | — | — | — | — | — |
493
494
  | devin | `~/.devin` | — | — | — | — | — | — |
494
- | agy | `~/.antigravity` | — | — | — | — | — | — |
495
+ | agy | `~/.gemini/config` | ✓ | — | — | — | — | ✓ `mcp_config.json` |
495
496
 
496
497
  Per-harness locations, mechanisms and the harness versions this was checked
497
498
  against are in [docs/harnesses.md](docs/harnesses.md).
@@ -1009,7 +1010,7 @@ agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on
1009
1010
 
1010
1011
  In the [settings menu](#settings--config-menu) an inline field commits on `Enter`, cancels on
1011
1012
  `Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
1012
- secrets (the same as `config test` / `config sync-ci`, and they need the extra). The bot token
1013
+ secrets (the same as `config test` / `config sync-ci`). The bot token
1013
1014
  is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
1014
1015
  through telegram-kit, never in a file or on a command line, and is shown masked. With no
1015
1016
  credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
@@ -1,3 +1,3 @@
1
1
  """agent-toggle: temporarily disable / re-enable AI-agent resources."""
2
2
 
3
- __version__ = "0.11.4"
3
+ __version__ = "0.12.1"
@@ -134,7 +134,7 @@ def read_project_mcp(file: Path) -> tuple[str, dict]:
134
134
  try:
135
135
  if file.stat().st_size > MAX_PROJECT_MCP:
136
136
  raise ProjectMcpError(f"{file} is larger than {MAX_PROJECT_MCP >> 20} MiB")
137
- text = file.read_bytes().decode("utf-8") # bytes: CRLF must survive
137
+ text = fs.read_utf8(file) # bytes: CRLF must survive
138
138
  except FileNotFoundError:
139
139
  raise ProjectMcpError(f"{file} not found") from None
140
140
  except (OSError, UnicodeDecodeError) as e:
@@ -696,7 +696,7 @@ def build_parser() -> argparse.ArgumentParser:
696
696
  sub.add_parser("update", parents=[common],
697
697
  help="check for a newer release; show the commands that update everything")
698
698
  cf = sub.add_parser("config", parents=[common],
699
- help="Telegram settings for the CI failure alerts (needs the telegram extra)")
699
+ help="Telegram settings for the CI failure alerts")
700
700
  cf.add_argument("action", nargs="?", choices=("test", "sync-ci"),
701
701
  help="test: send one message | sync-ci: set the GitHub repo secrets")
702
702
  cf.add_argument("--repo", metavar="OWNER/REPO", help="sync-ci: repository (default: this one)")
@@ -5,8 +5,8 @@ numbered list otherwise; `--json` prints every setting). Same shape as aicp's `-
5
5
  the bot token lives only in the OS credential store (Keychain / Secret Service / DPAPI,
6
6
  through telegram-kit, never in a file or an argument vector); the chat id and every other
7
7
  setting are ordinary configuration in ~/.agent-toggle/config.json (`settings`).
8
- telegram-kit is the optional `agent-toggle[telegram]` extra, imported only here: `test`
9
- and `sync-ci` need it, the menu works without it (its Telegram rows say so).
8
+ telegram-kit is a dependency, imported only here: `test` and `sync-ci` need it, and the menu
9
+ still works if a broken install lacks it (its Telegram rows say so).
10
10
  """
11
11
  from __future__ import annotations
12
12
 
@@ -20,7 +20,7 @@ SERVICE = "agent-toggle"
20
20
  CHAT_KEY = "telegram_chat_id"
21
21
  SECRETS = ("TELEGRAM_BOT_TOKEN", "TELEGRAM_CHAT_ID")
22
22
  CHAT_ID_RE = settings.CHAT_ID_RE
23
- INSTALL_HINT = "install the extra: uv tool install 'agent-toggle[telegram]'"
23
+ INSTALL_HINT = "reinstall agent-toggle: uv tool install --reinstall agent-toggle"
24
24
 
25
25
 
26
26
  config_file = settings.config_file
@@ -43,7 +43,7 @@ def kit():
43
43
 
44
44
 
45
45
  def optional_kit():
46
- """telegram-kit, or None without the extra (the menu degrades instead of exiting 4)."""
46
+ """telegram-kit, or None when it is missing (the menu degrades instead of exiting 4)."""
47
47
  try:
48
48
  return kit()
49
49
  except CliError:
@@ -312,7 +312,7 @@ def inventory(state: dict, table: dict, warn: Callable[[str], None] = lambda m:
312
312
  else (0, None, "flag entry; size unknown"))
313
313
  elif type_ == "mcp":
314
314
  counts = mcp_tool_counts(h)
315
- for name in live_mcp(h.home, h.backend):
315
+ for name in live_mcp(h.home, h.backend, h.mcp and h.mcp.file):
316
316
  add(hname, type_, name, True, mcp_estimate(hname, counts.get(name)))
317
317
 
318
318
  listed: list[str] = [] # canonical `name@marketplace` ids, for the parked rows
@@ -94,7 +94,7 @@ def _move_back(p: Path, live: Path) -> None:
94
94
  def _read_json(file: Path) -> tuple[object, str, str]:
95
95
  """(data, kind, why); kind is ok | missing | jsonc | broken."""
96
96
  try:
97
- text = file.read_bytes().decode("utf-8").removeprefix("\ufeff")
97
+ text = fs.read_utf8(file).removeprefix("\ufeff")
98
98
  except FileNotFoundError:
99
99
  return None, "missing", "is missing"
100
100
  except (OSError, UnicodeDecodeError) as e:
@@ -254,6 +254,15 @@ def lock():
254
254
  pass
255
255
 
256
256
 
257
+ def read_utf8(path: Path) -> str:
258
+ """Exact text of `path`; a git-crypt-locked file raises OSError naming the fix,
259
+ not an opaque UnicodeDecodeError."""
260
+ raw = path.read_bytes()
261
+ if raw.startswith(b"\x00GITCRYPT\x00"):
262
+ raise OSError("git-crypt encrypted -- run `git-crypt unlock` in its repo")
263
+ return raw.decode("utf-8")
264
+
265
+
257
266
  def atomic_write(path: Path, text: str, mode: int = 0o600, newline: str | None = None) -> None:
258
267
  """Write `text` to `path` via a same-dir tmp file created with `mode`.
259
268
  `newline=""` writes line endings as given (a user file edited in place)."""
@@ -173,9 +173,14 @@ def build(home: Path) -> dict[str, Harness]:
173
173
  # explicit not-applicable row: no dirs, no mechanisms, so every type
174
174
  # exits 4 through the ops dispatch.
175
175
  Harness("devin", home / ".devin", dirs={}, mechanisms={}),
176
- # DESIGN s11 q1: skills layout unobserved, so types=() and NO adapter yet
177
- # (`status` lists it as "found, nothing supported yet").
178
- Harness("agy", home / ".antigravity", dirs={}, mechanisms={}),
176
+ # DESIGN s11 q1 (answered on 1.3.3): global root `~/.gemini/config/` holds
177
+ # `skills/<name>/SKILL.md` and strict-JSON `mcp_config.json -> mcpServers`.
178
+ Harness("agy", home / ".gemini" / "config",
179
+ dirs={"skill": ("skills",)},
180
+ mechanisms={"skill": "move", "mcp": "remove_backup"},
181
+ mcp=McpSpec("json", home / ".gemini" / "config" / "mcp_config.json",
182
+ ("mcpServers",)),
183
+ editable=frozenset({"mcp_config.json"}), verified="1.3.3"),
179
184
  )}
180
185
 
181
186
 
@@ -54,7 +54,7 @@ CATALOG: dict[str, str] = {
54
54
  "config.language": "語言 / Language",
55
55
  "config.logo": "標誌",
56
56
  "config.more": "還有 %s 項",
57
- "config.needs_extra": "請安裝 agent-toggle[telegram]",
57
+ "config.needs_extra": "缺少 telegram-kit:請重新安裝 agent-toggle",
58
58
  "config.no_hidden": "此終端機無法隱藏輸入;請改在環境變數設定 TG_BOT_TOKEN",
59
59
  "config.no_output": "(沒有輸出)",
60
60
  "config.no_store": "這裡沒有作業系統憑證儲存區,權杖未儲存;請改設定 TG_BOT_TOKEN",
@@ -240,8 +240,9 @@ def live_names(base: Path, type_: str) -> list[str]:
240
240
  return sorted(set(names))
241
241
 
242
242
 
243
- def live_mcp(home: Path, backend: str | None) -> list[str]:
244
- """Names of the MCP servers currently configured for one harness."""
243
+ def live_mcp(home: Path, backend: str | None, file: Path | None = None) -> list[str]:
244
+ """Names of the MCP servers currently configured for one harness (`file`: the
245
+ table's McpSpec.file, which the `json` backend reads)."""
245
246
  if backend == "claude-json":
246
247
  try:
247
248
  cfg = json.loads(fs.claude_json().read_text(encoding="utf-8"))
@@ -260,9 +261,9 @@ def live_mcp(home: Path, backend: str | None) -> list[str]:
260
261
  except ProjectMcpError:
261
262
  return []
262
263
  return sorted(n for n in names if n.isprintable() and _valid_name(n))
263
- if backend == "json": # home is ~/.copilot; user-edited text, so filter names
264
+ if backend == "json" and file: # user-edited text, so filter names
264
265
  try:
265
- names = read_project_mcp(home / "mcp-config.json")[1]["mcpServers"]
266
+ names = read_project_mcp(file)[1]["mcpServers"]
266
267
  except ProjectMcpError:
267
268
  return []
268
269
  return sorted(n for n in names if n.isprintable() and _valid_name(n))
@@ -38,7 +38,7 @@ class Row:
38
38
  key: str = "" # the settings key (bool / choice / text)
39
39
  run: Callable[[Ctx, Result], None] | None = None # action rows
40
40
  confirm: Callable[[], str] | None = None # y/n question asked before the action runs
41
- telegram: bool = False # inert without the telegram extra
41
+ telegram: bool = False # inert when telegram-kit is missing
42
42
  harness: str = "" # harness rows: the name, for the detected note
43
43
  role: str = "title" # headings: each group in its own colour
44
44
 
@@ -265,7 +265,7 @@ def capture(fn: Callable[[Result], None]) -> list[Segment]:
265
265
 
266
266
 
267
267
  def needs_extra() -> str:
268
- return t("config.needs_extra", "install agent-toggle[telegram]")
268
+ return t("config.needs_extra", "telegram-kit missing: reinstall agent-toggle")
269
269
 
270
270
 
271
271
  def value_segments(ctx: Ctx, row: Row, g: theme.Glyphs) -> list[Segment]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-toggle
3
- Version: 0.11.4
3
+ Version: 0.12.1
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
@@ -24,6 +24,7 @@ Classifier: Topic :: Utilities
24
24
  Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
+ Requires-Dist: telegram-kit<0.3,>=0.2.2
27
28
  Provides-Extra: windows
28
29
  Requires-Dist: windows-curses; sys_platform == "win32" and extra == "windows"
29
30
  Provides-Extra: telegram
@@ -142,21 +143,20 @@ exits `1`. A shim written by a version before the marker counts as foreign;
142
143
  delete it and re-run. `--dry-run` shows the same plan, refusals included. The
143
144
  tool itself stays in one place.
144
145
 
145
- **Optional: Telegram alerts for CI** (for maintainers of a fork or clone). Install
146
- with the `telegram` extra, which adds [telegram-kit](https://pypi.org/project/telegram-kit/)
147
- (itself stdlib-only), then run the setup once; see [CI notifications](#ci-notifications):
146
+ **Optional: Telegram alerts for CI** (for maintainers of a fork or clone). The
147
+ Telegram support comes from [telegram-kit](https://pypi.org/project/telegram-kit/)
148
+ (itself stdlib-only), which every install includes, so a reinstall or upgrade keeps
149
+ your settings. Run the setup once; see [CI notifications](#ci-notifications):
148
150
 
149
151
  ```sh
150
- uv tool install "agent-toggle[telegram]" # or: pipx install "agent-toggle[telegram]"
151
- pip install -e ".[telegram]" # from a checkout
152
152
  agent-toggle config # settings menu: bot token + chat id live under Notifications
153
153
  agent-toggle config sync-ci # set the two GitHub repository secrets
154
154
  ```
155
155
 
156
- Without the extra everything else works exactly as before. Only `config test`,
157
- `config sync-ci` (both exit `4` without it) and the Telegram rows of the
158
- [settings menu](#settings--config-menu) need it; bare `config` still opens, and
159
- those rows read `install agent-toggle[telegram]`.
156
+ The old `agent-toggle[telegram]` extra still resolves and is now a no-op. If
157
+ telegram-kit is missing from a broken install, `config test` and `config sync-ci`
158
+ exit `4`, bare `config` still opens, and the Telegram rows read
159
+ `telegram-kit missing: reinstall agent-toggle`.
160
160
 
161
161
  **Optional: the Claude Code pane mod** (the picker as a pane inside Claude Code, claude harness
162
162
  only). It is not part of the PyPI package; with `agent-toggle` already on `PATH` from one of the
@@ -204,7 +204,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
204
204
  | `undo` | reverse the last logged batch (`--dry-run` shows the plan) |
205
205
  | `profile save\|apply\|diff\|list\|delete\|rename` | named sets of live items; see [Profiles](#profiles) |
206
206
  | `config` | settings menu (curses, else a numbered list); `config --json` lists every setting and the package version; see [Settings & config menu](#settings--config-menu) |
207
- | `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; needs the `[telegram]` extra; see [CI notifications](#ci-notifications) |
207
+ | `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; see [CI notifications](#ci-notifications) |
208
208
  | `help [command]` | styled help; `help ui` = `--help ui` = `ui --help`; see [Help](#help) |
209
209
  | `doctor` | check each harness layout and `state.json` against disk; exit `1` only on an `error` row; on a terminal, asks y/n per fix it can run itself |
210
210
  | `migrate` | import an older `~/.claude-toggle/` state, and move legacy `<home>/<dir>-disabled/` park dirs into `~/.agent-toggle/parked/user/` (see [Upgrading the park layout](#upgrading-the-park-layout)); safe to re-run |
@@ -391,6 +391,8 @@ a band above the prompt, buttons) and change Claude Code's behaviour. A plugin w
391
391
  `hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
392
392
  plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
393
393
 
394
+ ![Claude Mods: toggling mods from the picker](https://raw.githubusercontent.com/weskao/agent-toggle/main/docs/images/claude-mods-demo.gif)
395
+
394
396
  - The check reads the install path `claude plugin list` reports and every cached version
395
397
  under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
396
398
  To list the installed mods by hand:
@@ -463,8 +465,8 @@ invalid value in either is ignored with one warning on stderr.
463
465
  harness is switched off (with one warning on stderr);
464
466
  `disable` / `enable --project` without `--harness` still default to `claude`.
465
467
  - `language` translates the config menu, help, update prompt and picker chrome; command output stays English.
466
- - Bare `config` works without the `[telegram]` extra; `config test` and `config sync-ci` are
467
- unchanged and need it (exit `4` without it).
468
+ - telegram-kit is a regular dependency now, so reinstalling keeps the Telegram settings working;
469
+ `agent-toggle[telegram]` still resolves.
468
470
 
469
471
  ## Update check
470
472
 
@@ -523,7 +525,7 @@ logo, centred, when the window is tall enough to fit the whole menu beside it; t
523
525
  | copilot | `~/.copilot` | ✓ | ✓ | — | — | — | ✓ `mcp-config.json` |
524
526
  | vibe | `~/.vibe` | ✓ | — | — | — | — | — |
525
527
  | devin | `~/.devin` | — | — | — | — | — | — |
526
- | agy | `~/.antigravity` | — | — | — | — | — | — |
528
+ | agy | `~/.gemini/config` | ✓ | — | — | — | — | ✓ `mcp_config.json` |
527
529
 
528
530
  Per-harness locations, mechanisms and the harness versions this was checked
529
531
  against are in [docs/harnesses.md](docs/harnesses.md).
@@ -1041,7 +1043,7 @@ agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on
1041
1043
 
1042
1044
  In the [settings menu](#settings--config-menu) an inline field commits on `Enter`, cancels on
1043
1045
  `Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
1044
- secrets (the same as `config test` / `config sync-ci`, and they need the extra). The bot token
1046
+ secrets (the same as `config test` / `config sync-ci`). The bot token
1045
1047
  is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
1046
1048
  through telegram-kit, never in a file or on a command line, and is shown masked. With no
1047
1049
  credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
@@ -1,3 +1,4 @@
1
+ telegram-kit<0.3,>=0.2.2
1
2
 
2
3
  [telegram]
3
4
  telegram-kit<0.3,>=0.2.2
@@ -26,7 +26,8 @@ classifiers = [
26
26
  "Topic :: Utilities",
27
27
  ]
28
28
  requires-python = ">=3.10"
29
- dependencies = []
29
+ # telegram-kit is core (stdlib-only) so a plain reinstall can't drop the Telegram settings
30
+ dependencies = ["telegram-kit>=0.2.2,<0.3"]
30
31
 
31
32
  [project.urls]
32
33
  Homepage = "https://github.com/weskao/agent-toggle"
@@ -36,6 +37,7 @@ Changelog = "https://github.com/weskao/agent-toggle/blob/main/CHANGELOG.md"
36
37
 
37
38
  [project.optional-dependencies]
38
39
  windows = ["windows-curses; sys_platform == 'win32'"]
40
+ # kept so existing `agent-toggle[telegram]` install commands resolve without a warning
39
41
  telegram = ["telegram-kit>=0.2.2,<0.3"]
40
42
 
41
43
  [project.scripts]
@@ -162,6 +162,12 @@ class ProjectMcpJsonTest(SandboxCase):
162
162
  with self.assertRaises(mcp_json.ProjectMcpError):
163
163
  mcp_json.project_mcp_remove(self.put('{"mcpServers": {}}'), "a-mcp")
164
164
 
165
+ def test_git_crypt_locked_file_names_the_fix(self) -> None:
166
+ f = self.tmp / ".mcp.json"
167
+ f.write_bytes(b"\x00GITCRYPT\x00\xa4\xc7}")
168
+ with self.assertRaisesRegex(mcp_json.ProjectMcpError, "git-crypt unlock"):
169
+ mcp_json.read_project_mcp(f)
170
+
165
171
 
166
172
  class CopilotMcpTest(CliCase):
167
173
  """copilot's user-scope strict-JSON mcp-config.json (backend `json`)."""
@@ -163,13 +163,13 @@ class ConfigTest(CliCase):
163
163
  self.assertEqual(self.run_cli("config", "sync-ci", "--dry-run", "--repo", "o/r")[0], 0)
164
164
  self.assertEqual(self.gh_calls, [])
165
165
 
166
- def test_missing_extra_is_a_clear_exit_4_for_test_and_sync_ci_only(self) -> None:
166
+ def test_missing_kit_is_a_clear_exit_4_for_test_and_sync_ci_only(self) -> None:
167
167
  mock.patch.stopall()
168
168
  with mock.patch.dict(sys.modules, {"telegram_kit": None}):
169
169
  for action in ("test", "sync-ci"):
170
170
  rc, _, err = self.run_cli("config", action)
171
171
  self.assertEqual(rc, 4)
172
- self.assertIn("agent-toggle[telegram]", err)
172
+ self.assertIn("reinstall agent-toggle", err)
173
173
  with mock.patch.object(sys, "stdin", io.StringIO("")): # never the real TTY
174
174
  self.assertEqual(self.run_cli("config")[0], 0) # the menu degrades instead
175
175
 
@@ -260,7 +260,7 @@ class CursesMenuTest(MenuCase):
260
260
  def test_without_telegram_kit_the_rows_are_inert(self) -> None:
261
261
  win, rc = self.menu([*to("Telegram chat"), "\n", "r", "q"], telegram=False)
262
262
  self.assertEqual(rc, 0)
263
- self.assertIn("install agent-toggle[telegram]", win.text())
263
+ self.assertIn("reinstall agent-toggle", win.text())
264
264
  self.assertFalse(settings.config_file().exists())
265
265
 
266
266
  def test_tools_run_with_a_fresh_result_in_a_pager(self) -> None:
@@ -394,7 +394,7 @@ class NumberedTest(MenuCase):
394
394
  def test_without_telegram_kit_rows_are_degraded(self) -> None:
395
395
  rc, out = self.run_numbered(f"{number('Telegram chat')}\nq\n", telegram=False)
396
396
  self.assertEqual(rc, 0)
397
- self.assertIn("install agent-toggle[telegram]", out)
397
+ self.assertIn("reinstall agent-toggle", out)
398
398
  self.assertFalse(settings.config_file().exists())
399
399
 
400
400
  def test_actions_confirm_and_print_their_output(self) -> None:
@@ -431,7 +431,7 @@ class CliConfigMenuTest(CliCase):
431
431
  mock.patch.object(sys, "stdin", io.StringIO("1\n")):
432
432
  rc, out, err = self.run_cli("config")
433
433
  self.assertEqual(rc, 0, err)
434
- self.assertIn("install agent-toggle[telegram]", out)
434
+ self.assertIn("reinstall agent-toggle", out)
435
435
  self.assertIs(settings.get("update_check"), False)
436
436
  with mock.patch.dict(sys.modules, {"telegram_kit": None}):
437
437
  rc, env = self.run_json("config")
@@ -189,6 +189,16 @@ class CostCommandTest(CliCase):
189
189
  self.assertTrue(any("plugin list" in w for w in env["warnings"]))
190
190
 
191
191
 
192
+ class AgyCostTest(CliCase):
193
+ def test_live_skill_and_mcp_servers_are_listed(self) -> None:
194
+ shutil.copytree(FIXTURES / "agy", self.tmp, dirs_exist_ok=True)
195
+ rc, env = self.run_json("cost", "--harness", "agy")
196
+ self.assertEqual(rc, 0, env)
197
+ live = {(r["type"], r["name"]) for r in env["results"] if r["name"] and r["enabled"]}
198
+ self.assertEqual(live, {("skill", "demo-skill"), ("mcp", "example-mcp"),
199
+ ("mcp", "other-mcp")})
200
+
201
+
192
202
  class FlagCostTest(CliCase):
193
203
  def setUp(self) -> None:
194
204
  super().setUp()
@@ -17,7 +17,8 @@ TODAY = {
17
17
  } | {("opencode", "skill"), ("opencode", "command"),
18
18
  ("grok", "skill"), ("grok", "mcp"), ("openclaw", "skill"), ("openclaw", "agent"),
19
19
  ("openclaw", "plugin"), ("opencode", "mcp"), ("vibe", "skill"),
20
- ("copilot", "skill"), ("copilot", "agent"), ("copilot", "mcp")}
20
+ ("copilot", "skill"), ("copilot", "agent"), ("copilot", "mcp"),
21
+ ("agy", "skill"), ("agy", "mcp")}
21
22
 
22
23
 
23
24
  class HarnessTableTest(SandboxCase):
@@ -62,6 +63,10 @@ class HarnessTableTest(SandboxCase):
62
63
  self.assertEqual(t["copilot"].backend, "json")
63
64
  self.assertEqual(t["copilot"].mcp.file, self.tmp / ".copilot" / "mcp-config.json")
64
65
  self.assertEqual(t["copilot"].editable, {"mcp-config.json"})
66
+ self.assertEqual(t["agy"].home, self.tmp / ".gemini" / "config")
67
+ self.assertEqual(t["agy"].backend, "json")
68
+ self.assertEqual(t["agy"].mcp.file, self.tmp / ".gemini" / "config" / "mcp_config.json")
69
+ self.assertEqual(t["agy"].editable, {"mcp_config.json"})
65
70
 
66
71
  def test_flag_pairs_are_declared_and_editable(self) -> None:
67
72
  for n, h in build(self.tmp).items():
File without changes
File without changes