agent-toggle 0.12.1__tar.gz → 0.13.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.
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/PKG-INFO +57 -36
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/README.md +56 -35
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/__init__.py +1 -1
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/backends/plugin_cli.py +4 -3
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/cli.py +18 -2
- agent_toggle-0.13.0/agent_toggle/completions.py +210 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/doctor.py +4 -2
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/harnesses.py +13 -19
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/help.py +11 -1
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_cli.py +4 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/mechanisms.py +283 -107
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ops.py +5 -4
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/store.py +4 -3
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/config_menu.py +12 -6
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle.egg-info/PKG-INFO +57 -36
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle.egg-info/SOURCES.txt +1 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_alias.py +2 -2
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_backends.py +23 -5
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_cli_surface.py +9 -0
- agent_toggle-0.13.0/tests/test_completions.py +98 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_config.py +11 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_config_menu.py +32 -7
- agent_toggle-0.13.0/tests/test_crash.py +1058 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_harnesses.py +22 -0
- agent_toggle-0.13.0/tests/test_mechanisms.py +199 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_secret_audit.py +62 -0
- agent_toggle-0.12.1/tests/test_completions.py +0 -37
- agent_toggle-0.12.1/tests/test_crash.py +0 -382
- agent_toggle-0.12.1/tests/test_mechanisms.py +0 -79
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/LICENSE +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/__main__.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/backends/__init__.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/backends/flag_json.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/backends/mcp_json.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/backends/mcp_toml.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/companions.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/config.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/cost.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/fs.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/i18n.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/locales/__init__.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_common.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_config.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_picker.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/output.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/profiles.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/settings.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/shims/claude.md.tmpl +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/shims/generic.md.tmpl +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/spinner.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/toml_check.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/__init__.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/logo.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/menu.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/model.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/picker.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/ui/theme.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/undo.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/update_check.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle/update_prompt.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle.egg-info/dependency_links.txt +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle.egg-info/entry_points.txt +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle.egg-info/requires.txt +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/agent_toggle.egg-info/top_level.txt +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/pyproject.toml +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/setup.cfg +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_all_type.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_cli.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_color.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_companions.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_conformance.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_containment.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_cost.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_doctor.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_encoding.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_fs.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_help.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_i18n.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_install.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_logo.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_menu.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_mods.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_picker.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_platform.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_profiles.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_project.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_safety.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_same_name.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_settings.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_store.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_theme.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_undo.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/tests/test_update_check.py +0 -0
- {agent_toggle-0.12.1 → agent_toggle-0.13.0}/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.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -59,10 +59,11 @@ language, update check and Telegram notifications. It also runs tools such as `d
|
|
|
59
59
|
|
|
60
60
|

|
|
61
61
|
|
|
62
|
-
**`/agent-toggle` inside Claude Code**: the same picker as a pane,
|
|
63
|
-
servers, plugins and mods on or off without leaving the session
|
|
62
|
+
**`/agent-toggle` inside Claude Code**: the same picker as a pane. Search, then turn skills, MCP
|
|
63
|
+
servers, plugins and mods on or off without leaving the session; each toggle applies at once and
|
|
64
|
+
queues the matching reload ([Claude Code pane mod](#claude-code-pane-mod)).
|
|
64
65
|
|
|
65
|
-

|
|
66
67
|
|
|
67
68
|
## Why
|
|
68
69
|
|
|
@@ -146,17 +147,12 @@ tool itself stays in one place.
|
|
|
146
147
|
**Optional: Telegram alerts for CI** (for maintainers of a fork or clone). The
|
|
147
148
|
Telegram support comes from [telegram-kit](https://pypi.org/project/telegram-kit/)
|
|
148
149
|
(itself stdlib-only), which every install includes, so a reinstall or upgrade keeps
|
|
149
|
-
your settings.
|
|
150
|
-
|
|
151
|
-
```sh
|
|
152
|
-
agent-toggle config # settings menu: bot token + chat id live under Notifications
|
|
153
|
-
agent-toggle config sync-ci # set the two GitHub repository secrets
|
|
154
|
-
```
|
|
150
|
+
your settings. The settings menu hides its Telegram rows for now; set up the alerts as in
|
|
151
|
+
[CI notifications](#ci-notifications).
|
|
155
152
|
|
|
156
153
|
The old `agent-toggle[telegram]` extra still resolves and is now a no-op. If
|
|
157
154
|
telegram-kit is missing from a broken install, `config test` and `config sync-ci`
|
|
158
|
-
exit `4
|
|
159
|
-
`telegram-kit missing: reinstall agent-toggle`.
|
|
155
|
+
exit `4`.
|
|
160
156
|
|
|
161
157
|
**Optional: the Claude Code pane mod** (the picker as a pane inside Claude Code, claude harness
|
|
162
158
|
only). It is not part of the PyPI package; with `agent-toggle` already on `PATH` from one of the
|
|
@@ -172,16 +168,27 @@ See [Claude Code pane mod](#claude-code-pane-mod).
|
|
|
172
168
|
|
|
173
169
|
## Shell completion
|
|
174
170
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
`agent-toggle completion <shell>` prints a static completion script, built from the CLI
|
|
172
|
+
parser, for bash, zsh, fish or PowerShell. Save it once where your shell loads it, and
|
|
173
|
+
run the same line again after an upgrade so new commands complete:
|
|
178
174
|
|
|
179
175
|
```sh
|
|
180
|
-
|
|
176
|
+
# zsh: needs fpath=(~/.zfunc $fpath) before compinit in ~/.zshrc
|
|
177
|
+
mkdir -p ~/.zfunc && agent-toggle completion zsh > ~/.zfunc/_agent-toggle
|
|
178
|
+
# bash: loaded on demand by the bash-completion package
|
|
179
|
+
mkdir -p ~/.local/share/bash-completion/completions && agent-toggle completion bash > ~/.local/share/bash-completion/completions/agent-toggle
|
|
180
|
+
# fish
|
|
181
|
+
agent-toggle completion fish > ~/.config/fish/completions/agent-toggle.fish
|
|
181
182
|
```
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
```powershell
|
|
185
|
+
# PowerShell: save the script, then load it from your profile
|
|
186
|
+
agent-toggle completion powershell > "$HOME\agent-toggle.ps1"; Add-Content $PROFILE '. "$HOME\agent-toggle.ps1"'
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Without the bash-completion package, add `source <file>` to `~/.bashrc` instead.
|
|
190
|
+
`python3 tools/gen_completions.py completions` writes all four files from a checkout; the
|
|
191
|
+
Release workflow uploads them as its `completions` artifact.
|
|
185
192
|
|
|
186
193
|
## Usage
|
|
187
194
|
|
|
@@ -258,7 +265,9 @@ unshared).
|
|
|
258
265
|
`--dry-run` (`disable` / `enable` / `enable --all` / `undo` / `profile apply` /
|
|
259
266
|
`install-shims` / `ui`) computes the plan --
|
|
260
267
|
moves, companions, backups, MCP edits, warnings -- and writes nothing: no state,
|
|
261
|
-
log, lock or backup, and no chmod; it never shells out to `claude
|
|
268
|
+
log, lock or backup, and no chmod; it never shells out to `claude`, except one
|
|
269
|
+
read-only `claude plugin list --json` (up to 30 s) when a killed run left a plugin
|
|
270
|
+
op pending and has to be settled. `disable` /
|
|
262
271
|
`enable` result rows have action `would-disable` / `would-enable`; every plan
|
|
263
272
|
row has status `planned`. Read-only commands (`list`, `status`, `cost`) also
|
|
264
273
|
change nothing on disk -- `status` warns about a `state.json` or
|
|
@@ -391,8 +400,6 @@ a band above the prompt, buttons) and change Claude Code's behaviour. A plugin w
|
|
|
391
400
|
`hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
|
|
392
401
|
plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
|
|
393
402
|
|
|
394
|
-

|
|
395
|
-
|
|
396
403
|
- The check reads the install path `claude plugin list` reports and every cached version
|
|
397
404
|
under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
|
|
398
405
|
To list the installed mods by hand:
|
|
@@ -428,8 +435,7 @@ end of input, `q` or an empty line exits `0`
|
|
|
428
435
|
| General | Check for updates, Color, Logo, Language, Default harness |
|
|
429
436
|
| Harnesses | one On/Off per harness, with `found ~/.x` or `not on this machine` |
|
|
430
437
|
| Picker | default sort, harness filter, type filter |
|
|
431
|
-
|
|
|
432
|
-
| Tools | Health check (`doctor`; then asks y/n per fix it can run), Install shims, Undo last change (asks y/n), Send test message, Sync CI secrets (asks y/n); their output shows in an overlay |
|
|
438
|
+
| Tools | Health check (`doctor`; then asks y/n per fix it can run), Install shims, Undo last change (asks y/n); their output shows in an overlay |
|
|
433
439
|
|
|
434
440
|
Keys: `↑` `↓` move (wraps, skips headings), `←` `→` cycle a value, `Enter` / `Space` change or run
|
|
435
441
|
the row, `r` reset the row, `R` reset everything (asks y/n), `q` / `Esc` / `Ctrl-C` quit. In an
|
|
@@ -568,7 +574,7 @@ rather than guessing.
|
|
|
568
574
|
|
|
569
575
|
OpenCode may read skills from another harness's directory through
|
|
570
576
|
`opencode.json` → `skills.paths` (absolute, `~`, `$HOME/...`
|
|
571
|
-
and `${HOME}/...` entries; read from `opencode.json
|
|
577
|
+
and `${HOME}/...` entries; read from `opencode.json` and `opencode.jsonc` (OpenCode loads both and merges them);
|
|
572
578
|
relative entries are skipped, since OpenCode resolves them against the session
|
|
573
579
|
directory). OpenCode also always scans `~/.claude/skills` and `~/.agents/skills`,
|
|
574
580
|
so claude's skills count as shared with it. Such a directory is **one** item, filed under
|
|
@@ -576,6 +582,11 @@ its owner (the harness whose home really holds it): it is
|
|
|
576
582
|
parked once, tracked once, and every row reports `shared_with`, the other
|
|
577
583
|
harnesses it also affects. `status` prints `shared dir with: ...`.
|
|
578
584
|
|
|
585
|
+
Disabling an OpenCode skill while a same-named copy sits in another scanned dir warns
|
|
586
|
+
that OpenCode still loads that copy (stderr, and `warnings` in `--json`). Two copies of
|
|
587
|
+
one name under one owner share one record, so a second `disable` is refused with
|
|
588
|
+
`another copy is already parked at <path> -- enable it first`.
|
|
589
|
+
|
|
579
590
|
If the live directory carries `.synced-from-*` markers, a sync job may
|
|
580
591
|
re-create what you parked; `status` and `disable` warn about it. Park in the
|
|
581
592
|
source harness instead. The warning is printed once per real directory, naming
|
|
@@ -875,10 +886,11 @@ a warning and a `recovered` log row (which `undo` does not reverse). `status` an
|
|
|
875
886
|
`doctor` report it, with `agent-toggle enable ...` when the item ends up
|
|
876
887
|
disabled. The one case left to you is a cross-filesystem move killed between its
|
|
877
888
|
two renames: both copies are complete, and the report names the `diff -r` to
|
|
878
|
-
check and the `rm -rf` that keeps either one. MCP
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
889
|
+
check and the `rm -rf` that keeps either one. MCP servers (user-scope json,
|
|
890
|
+
claude-json, toml, project `.mcp.json`) and claude plugins have a `pending` record
|
|
891
|
+
too (a plugin is settled from a live `claude plugin list --json`); only a
|
|
892
|
+
`claude.ai` connector toggle is saved per item (when the next one starts), and a
|
|
893
|
+
kill inside that window leaves its change unrecorded.
|
|
882
894
|
A killed run also leaves its `lock`; the next run's error says so.
|
|
883
895
|
|
|
884
896
|
Each `log.jsonl` row is `{ts, harness, type, name, action, result, batch,
|
|
@@ -1029,21 +1041,24 @@ it on macOS, Linux and Windows.
|
|
|
1029
1041
|
|
|
1030
1042
|
## CI notifications
|
|
1031
1043
|
|
|
1032
|
-
|
|
1033
|
-
push (a lint-only failure does not page).
|
|
1034
|
-
|
|
1044
|
+
CI and the Release workflow can send a Telegram message when they fail: `ci.yml` when the
|
|
1045
|
+
test matrix fails on a push (a lint-only failure does not page), `release.yml` when the
|
|
1046
|
+
build, PyPI publish or post-publish smoke test fails. Alerts never fire for pull requests or
|
|
1047
|
+
green runs. Both call `.github/workflows/notify-telegram.yml`, and the message names the
|
|
1048
|
+
failure, repository, tag or branch, 7-character commit and a link to the run.
|
|
1035
1049
|
|
|
1036
|
-
|
|
1050
|
+
The [settings menu](#settings--config-menu) hides its Telegram rows (bot token, chat ID,
|
|
1051
|
+
Send test message, Sync CI secrets) for now; `SHOW_TELEGRAM` in `agent_toggle/ui/config_menu.py`
|
|
1052
|
+
brings them back. `config test` and `config sync-ci` still work: they read a bot token and
|
|
1053
|
+
chat ID saved earlier, else `TG_BOT_TOKEN` / `TG_CHAT_ID` from the environment. Without
|
|
1054
|
+
either, set the secrets by hand (below).
|
|
1037
1055
|
|
|
1038
1056
|
```sh
|
|
1039
|
-
agent-toggle config # settings menu: Notifications has the bot token (hidden) and chat id
|
|
1040
1057
|
agent-toggle config test # send one test message
|
|
1041
1058
|
agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on the repo
|
|
1042
1059
|
```
|
|
1043
1060
|
|
|
1044
|
-
|
|
1045
|
-
`Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
|
|
1046
|
-
secrets (the same as `config test` / `config sync-ci`). The bot token
|
|
1061
|
+
When the rows are shown, the bot token
|
|
1047
1062
|
is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
|
|
1048
1063
|
through telegram-kit, never in a file or on a command line, and is shown masked. With no
|
|
1049
1064
|
credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
|
|
@@ -1059,6 +1074,12 @@ gh secret set TELEGRAM_BOT_TOKEN
|
|
|
1059
1074
|
gh secret set TELEGRAM_CHAT_ID
|
|
1060
1075
|
```
|
|
1061
1076
|
|
|
1077
|
+
To check the repository secrets themselves, send a test message through the same workflow:
|
|
1078
|
+
|
|
1079
|
+
```sh
|
|
1080
|
+
gh workflow run notify-telegram.yml # optional: -f title="..."
|
|
1081
|
+
```
|
|
1082
|
+
|
|
1062
1083
|
With either secret unset (a fork, or a clone you have not configured) the notify job
|
|
1063
1084
|
prints a `::notice::` and exits 0, so it never adds a second red X.
|
|
1064
1085
|
|
|
@@ -26,10 +26,11 @@ language, update check and Telegram notifications. It also runs tools such as `d
|
|
|
26
26
|
|
|
27
27
|

|
|
28
28
|
|
|
29
|
-
**`/agent-toggle` inside Claude Code**: the same picker as a pane,
|
|
30
|
-
servers, plugins and mods on or off without leaving the session
|
|
29
|
+
**`/agent-toggle` inside Claude Code**: the same picker as a pane. Search, then turn skills, MCP
|
|
30
|
+
servers, plugins and mods on or off without leaving the session; each toggle applies at once and
|
|
31
|
+
queues the matching reload ([Claude Code pane mod](#claude-code-pane-mod)).
|
|
31
32
|
|
|
32
|
-

|
|
33
34
|
|
|
34
35
|
## Why
|
|
35
36
|
|
|
@@ -113,17 +114,12 @@ tool itself stays in one place.
|
|
|
113
114
|
**Optional: Telegram alerts for CI** (for maintainers of a fork or clone). The
|
|
114
115
|
Telegram support comes from [telegram-kit](https://pypi.org/project/telegram-kit/)
|
|
115
116
|
(itself stdlib-only), which every install includes, so a reinstall or upgrade keeps
|
|
116
|
-
your settings.
|
|
117
|
-
|
|
118
|
-
```sh
|
|
119
|
-
agent-toggle config # settings menu: bot token + chat id live under Notifications
|
|
120
|
-
agent-toggle config sync-ci # set the two GitHub repository secrets
|
|
121
|
-
```
|
|
117
|
+
your settings. The settings menu hides its Telegram rows for now; set up the alerts as in
|
|
118
|
+
[CI notifications](#ci-notifications).
|
|
122
119
|
|
|
123
120
|
The old `agent-toggle[telegram]` extra still resolves and is now a no-op. If
|
|
124
121
|
telegram-kit is missing from a broken install, `config test` and `config sync-ci`
|
|
125
|
-
exit `4
|
|
126
|
-
`telegram-kit missing: reinstall agent-toggle`.
|
|
122
|
+
exit `4`.
|
|
127
123
|
|
|
128
124
|
**Optional: the Claude Code pane mod** (the picker as a pane inside Claude Code, claude harness
|
|
129
125
|
only). It is not part of the PyPI package; with `agent-toggle` already on `PATH` from one of the
|
|
@@ -139,16 +135,27 @@ See [Claude Code pane mod](#claude-code-pane-mod).
|
|
|
139
135
|
|
|
140
136
|
## Shell completion
|
|
141
137
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
`agent-toggle completion <shell>` prints a static completion script, built from the CLI
|
|
139
|
+
parser, for bash, zsh, fish or PowerShell. Save it once where your shell loads it, and
|
|
140
|
+
run the same line again after an upgrade so new commands complete:
|
|
145
141
|
|
|
146
142
|
```sh
|
|
147
|
-
|
|
143
|
+
# zsh: needs fpath=(~/.zfunc $fpath) before compinit in ~/.zshrc
|
|
144
|
+
mkdir -p ~/.zfunc && agent-toggle completion zsh > ~/.zfunc/_agent-toggle
|
|
145
|
+
# bash: loaded on demand by the bash-completion package
|
|
146
|
+
mkdir -p ~/.local/share/bash-completion/completions && agent-toggle completion bash > ~/.local/share/bash-completion/completions/agent-toggle
|
|
147
|
+
# fish
|
|
148
|
+
agent-toggle completion fish > ~/.config/fish/completions/agent-toggle.fish
|
|
148
149
|
```
|
|
149
150
|
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
```powershell
|
|
152
|
+
# PowerShell: save the script, then load it from your profile
|
|
153
|
+
agent-toggle completion powershell > "$HOME\agent-toggle.ps1"; Add-Content $PROFILE '. "$HOME\agent-toggle.ps1"'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Without the bash-completion package, add `source <file>` to `~/.bashrc` instead.
|
|
157
|
+
`python3 tools/gen_completions.py completions` writes all four files from a checkout; the
|
|
158
|
+
Release workflow uploads them as its `completions` artifact.
|
|
152
159
|
|
|
153
160
|
## Usage
|
|
154
161
|
|
|
@@ -225,7 +232,9 @@ unshared).
|
|
|
225
232
|
`--dry-run` (`disable` / `enable` / `enable --all` / `undo` / `profile apply` /
|
|
226
233
|
`install-shims` / `ui`) computes the plan --
|
|
227
234
|
moves, companions, backups, MCP edits, warnings -- and writes nothing: no state,
|
|
228
|
-
log, lock or backup, and no chmod; it never shells out to `claude
|
|
235
|
+
log, lock or backup, and no chmod; it never shells out to `claude`, except one
|
|
236
|
+
read-only `claude plugin list --json` (up to 30 s) when a killed run left a plugin
|
|
237
|
+
op pending and has to be settled. `disable` /
|
|
229
238
|
`enable` result rows have action `would-disable` / `would-enable`; every plan
|
|
230
239
|
row has status `planned`. Read-only commands (`list`, `status`, `cost`) also
|
|
231
240
|
change nothing on disk -- `status` warns about a `state.json` or
|
|
@@ -358,8 +367,6 @@ a band above the prompt, buttons) and change Claude Code's behaviour. A plugin w
|
|
|
358
367
|
`hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
|
|
359
368
|
plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
|
|
360
369
|
|
|
361
|
-

|
|
362
|
-
|
|
363
370
|
- The check reads the install path `claude plugin list` reports and every cached version
|
|
364
371
|
under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
|
|
365
372
|
To list the installed mods by hand:
|
|
@@ -395,8 +402,7 @@ end of input, `q` or an empty line exits `0`
|
|
|
395
402
|
| General | Check for updates, Color, Logo, Language, Default harness |
|
|
396
403
|
| Harnesses | one On/Off per harness, with `found ~/.x` or `not on this machine` |
|
|
397
404
|
| Picker | default sort, harness filter, type filter |
|
|
398
|
-
|
|
|
399
|
-
| Tools | Health check (`doctor`; then asks y/n per fix it can run), Install shims, Undo last change (asks y/n), Send test message, Sync CI secrets (asks y/n); their output shows in an overlay |
|
|
405
|
+
| Tools | Health check (`doctor`; then asks y/n per fix it can run), Install shims, Undo last change (asks y/n); their output shows in an overlay |
|
|
400
406
|
|
|
401
407
|
Keys: `↑` `↓` move (wraps, skips headings), `←` `→` cycle a value, `Enter` / `Space` change or run
|
|
402
408
|
the row, `r` reset the row, `R` reset everything (asks y/n), `q` / `Esc` / `Ctrl-C` quit. In an
|
|
@@ -535,7 +541,7 @@ rather than guessing.
|
|
|
535
541
|
|
|
536
542
|
OpenCode may read skills from another harness's directory through
|
|
537
543
|
`opencode.json` → `skills.paths` (absolute, `~`, `$HOME/...`
|
|
538
|
-
and `${HOME}/...` entries; read from `opencode.json
|
|
544
|
+
and `${HOME}/...` entries; read from `opencode.json` and `opencode.jsonc` (OpenCode loads both and merges them);
|
|
539
545
|
relative entries are skipped, since OpenCode resolves them against the session
|
|
540
546
|
directory). OpenCode also always scans `~/.claude/skills` and `~/.agents/skills`,
|
|
541
547
|
so claude's skills count as shared with it. Such a directory is **one** item, filed under
|
|
@@ -543,6 +549,11 @@ its owner (the harness whose home really holds it): it is
|
|
|
543
549
|
parked once, tracked once, and every row reports `shared_with`, the other
|
|
544
550
|
harnesses it also affects. `status` prints `shared dir with: ...`.
|
|
545
551
|
|
|
552
|
+
Disabling an OpenCode skill while a same-named copy sits in another scanned dir warns
|
|
553
|
+
that OpenCode still loads that copy (stderr, and `warnings` in `--json`). Two copies of
|
|
554
|
+
one name under one owner share one record, so a second `disable` is refused with
|
|
555
|
+
`another copy is already parked at <path> -- enable it first`.
|
|
556
|
+
|
|
546
557
|
If the live directory carries `.synced-from-*` markers, a sync job may
|
|
547
558
|
re-create what you parked; `status` and `disable` warn about it. Park in the
|
|
548
559
|
source harness instead. The warning is printed once per real directory, naming
|
|
@@ -842,10 +853,11 @@ a warning and a `recovered` log row (which `undo` does not reverse). `status` an
|
|
|
842
853
|
`doctor` report it, with `agent-toggle enable ...` when the item ends up
|
|
843
854
|
disabled. The one case left to you is a cross-filesystem move killed between its
|
|
844
855
|
two renames: both copies are complete, and the report names the `diff -r` to
|
|
845
|
-
check and the `rm -rf` that keeps either one. MCP
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
856
|
+
check and the `rm -rf` that keeps either one. MCP servers (user-scope json,
|
|
857
|
+
claude-json, toml, project `.mcp.json`) and claude plugins have a `pending` record
|
|
858
|
+
too (a plugin is settled from a live `claude plugin list --json`); only a
|
|
859
|
+
`claude.ai` connector toggle is saved per item (when the next one starts), and a
|
|
860
|
+
kill inside that window leaves its change unrecorded.
|
|
849
861
|
A killed run also leaves its `lock`; the next run's error says so.
|
|
850
862
|
|
|
851
863
|
Each `log.jsonl` row is `{ts, harness, type, name, action, result, batch,
|
|
@@ -996,21 +1008,24 @@ it on macOS, Linux and Windows.
|
|
|
996
1008
|
|
|
997
1009
|
## CI notifications
|
|
998
1010
|
|
|
999
|
-
|
|
1000
|
-
push (a lint-only failure does not page).
|
|
1001
|
-
|
|
1011
|
+
CI and the Release workflow can send a Telegram message when they fail: `ci.yml` when the
|
|
1012
|
+
test matrix fails on a push (a lint-only failure does not page), `release.yml` when the
|
|
1013
|
+
build, PyPI publish or post-publish smoke test fails. Alerts never fire for pull requests or
|
|
1014
|
+
green runs. Both call `.github/workflows/notify-telegram.yml`, and the message names the
|
|
1015
|
+
failure, repository, tag or branch, 7-character commit and a link to the run.
|
|
1002
1016
|
|
|
1003
|
-
|
|
1017
|
+
The [settings menu](#settings--config-menu) hides its Telegram rows (bot token, chat ID,
|
|
1018
|
+
Send test message, Sync CI secrets) for now; `SHOW_TELEGRAM` in `agent_toggle/ui/config_menu.py`
|
|
1019
|
+
brings them back. `config test` and `config sync-ci` still work: they read a bot token and
|
|
1020
|
+
chat ID saved earlier, else `TG_BOT_TOKEN` / `TG_CHAT_ID` from the environment. Without
|
|
1021
|
+
either, set the secrets by hand (below).
|
|
1004
1022
|
|
|
1005
1023
|
```sh
|
|
1006
|
-
agent-toggle config # settings menu: Notifications has the bot token (hidden) and chat id
|
|
1007
1024
|
agent-toggle config test # send one test message
|
|
1008
1025
|
agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on the repo
|
|
1009
1026
|
```
|
|
1010
1027
|
|
|
1011
|
-
|
|
1012
|
-
`Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
|
|
1013
|
-
secrets (the same as `config test` / `config sync-ci`). The bot token
|
|
1028
|
+
When the rows are shown, the bot token
|
|
1014
1029
|
is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
|
|
1015
1030
|
through telegram-kit, never in a file or on a command line, and is shown masked. With no
|
|
1016
1031
|
credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
|
|
@@ -1026,6 +1041,12 @@ gh secret set TELEGRAM_BOT_TOKEN
|
|
|
1026
1041
|
gh secret set TELEGRAM_CHAT_ID
|
|
1027
1042
|
```
|
|
1028
1043
|
|
|
1044
|
+
To check the repository secrets themselves, send a test message through the same workflow:
|
|
1045
|
+
|
|
1046
|
+
```sh
|
|
1047
|
+
gh workflow run notify-telegram.yml # optional: -f title="..."
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1029
1050
|
With either secret unset (a fork, or a clone you have not configured) the notify job
|
|
1030
1051
|
prints a `::notice::` and exits 0, so it never adds a second red X.
|
|
1031
1052
|
|
|
@@ -59,8 +59,8 @@ def run_cli(binary: str | None, args: list[str], cwd: str | None = None,
|
|
|
59
59
|
return False, "CLI not found on PATH"
|
|
60
60
|
limit = _timeout(timeout)
|
|
61
61
|
try:
|
|
62
|
-
p = runner([binary, *args], capture_output=True, text=True,
|
|
63
|
-
timeout=limit, cwd=cwd)
|
|
62
|
+
p = runner([binary, *args], capture_output=True, text=True, encoding="utf-8",
|
|
63
|
+
errors="replace", timeout=limit, cwd=cwd) # Windows: not cp1252
|
|
64
64
|
except subprocess.TimeoutExpired:
|
|
65
65
|
return False, f"CLI timed out after {limit:g}s (raise it with {TIMEOUT_ENV}=<seconds>)"
|
|
66
66
|
except OSError as e: # cwd gone, binary unexecutable
|
|
@@ -79,7 +79,8 @@ def list_plugins(warn: Callable[[str], None]) -> list[dict]:
|
|
|
79
79
|
except ValueError:
|
|
80
80
|
data = None
|
|
81
81
|
if not isinstance(data, list):
|
|
82
|
-
|
|
82
|
+
why = f" ({text.splitlines()[-1][:120]})" if not ok and text else "" # e.g. a timeout
|
|
83
|
+
warn(f"could not read `claude plugin list --json`{why}; plugin rows come from state only")
|
|
83
84
|
return []
|
|
84
85
|
return [p for p in data if isinstance(p, dict) and isinstance(p.get("id"), str)]
|
|
85
86
|
|
|
@@ -51,6 +51,7 @@ from pathlib import Path
|
|
|
51
51
|
|
|
52
52
|
from . import (
|
|
53
53
|
__version__,
|
|
54
|
+
completions,
|
|
54
55
|
config,
|
|
55
56
|
cost,
|
|
56
57
|
doctor,
|
|
@@ -558,7 +559,7 @@ def _stale_ignore_lines(h, args: argparse.Namespace, out: Result) -> None:
|
|
|
558
559
|
|
|
559
560
|
|
|
560
561
|
COMMANDS = ("ui", "pick", "status", "list", "cost", "migrate", "disable", "enable",
|
|
561
|
-
"install-shims", "profile", "undo", "doctor", "config", "update")
|
|
562
|
+
"install-shims", "profile", "undo", "doctor", "config", "update", "completion")
|
|
562
563
|
_GLOBAL_FLAGS = ("--json", "-v", "--verbose")
|
|
563
564
|
|
|
564
565
|
|
|
@@ -701,6 +702,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
701
702
|
help="test: send one message | sync-ci: set the GitHub repo secrets")
|
|
702
703
|
cf.add_argument("--repo", metavar="OWNER/REPO", help="sync-ci: repository (default: this one)")
|
|
703
704
|
cf.add_argument("--dry-run", action="store_true", help="sync-ci: show the plan; set nothing")
|
|
705
|
+
cm = sub.add_parser("completion", parents=[common],
|
|
706
|
+
help="print the shell completion script")
|
|
707
|
+
cm.add_argument("shell", choices=list(completions.SHELLS))
|
|
704
708
|
up2 = sub.add_parser("undo", parents=[common], help="reverse the last logged batch")
|
|
705
709
|
up2.add_argument("--dry-run", action="store_true", help="show the plan; change nothing")
|
|
706
710
|
for name, verb in (("disable", "park"), ("enable", "restore")):
|
|
@@ -730,7 +734,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
730
734
|
i18n.set_language(language)
|
|
731
735
|
color = scan_color(argv, color) # --color > env > setting > auto
|
|
732
736
|
i = _command_index(argv)
|
|
733
|
-
|
|
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()
|
|
734
739
|
try:
|
|
735
740
|
return _main(argv, color)
|
|
736
741
|
except KeyboardInterrupt:
|
|
@@ -802,6 +807,17 @@ def _main(argv: list[str], color: str) -> int:
|
|
|
802
807
|
update_prompt.cmd_update(out)
|
|
803
808
|
elif cmd == "config":
|
|
804
809
|
config.cmd_config(args, out)
|
|
810
|
+
elif cmd == "completion":
|
|
811
|
+
if out.json_mode:
|
|
812
|
+
die("completion prints a shell script; --json is not supported", 2)
|
|
813
|
+
text = completions.script(args.shell, build_parser())
|
|
814
|
+
buf = getattr(sys.stdout, "buffer", None)
|
|
815
|
+
if buf is None: # a StringIO: no newline translation to dodge
|
|
816
|
+
print(text, end="")
|
|
817
|
+
else: # bytes: Windows text stdout would write \r\n,
|
|
818
|
+
sys.stdout.flush() # which bash rejects
|
|
819
|
+
buf.write(text.encode("utf-8"))
|
|
820
|
+
buf.flush()
|
|
805
821
|
elif cmd == "undo":
|
|
806
822
|
undo.cmd_undo(args.dry_run, args.harness, out)
|
|
807
823
|
elif cmd == "enable" and args.all:
|