agent-toggle 0.12.0__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.0 → agent_toggle-0.13.0}/PKG-INFO +57 -34
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/README.md +56 -33
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/__init__.py +1 -1
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/backends/mcp_json.py +1 -1
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/backends/plugin_cli.py +4 -3
- {agent_toggle-0.12.0 → 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.0 → agent_toggle-0.13.0}/agent_toggle/doctor.py +5 -3
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/fs.py +9 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/harnesses.py +13 -19
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/help.py +11 -1
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_cli.py +4 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/mechanisms.py +283 -107
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ops.py +5 -4
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/store.py +4 -3
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/config_menu.py +12 -6
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle.egg-info/PKG-INFO +57 -34
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle.egg-info/SOURCES.txt +1 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_alias.py +2 -2
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_backends.py +29 -5
- {agent_toggle-0.12.0 → 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.0 → agent_toggle-0.13.0}/tests/test_config.py +11 -0
- {agent_toggle-0.12.0 → 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.0 → 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.0 → agent_toggle-0.13.0}/tests/test_secret_audit.py +62 -0
- agent_toggle-0.12.0/tests/test_completions.py +0 -37
- agent_toggle-0.12.0/tests/test_crash.py +0 -382
- agent_toggle-0.12.0/tests/test_mechanisms.py +0 -79
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/LICENSE +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/__main__.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/backends/__init__.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/backends/flag_json.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/backends/mcp_toml.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/companions.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/config.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/cost.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/i18n.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/locales/__init__.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_common.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_config.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/locales/zh_tw_picker.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/output.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/profiles.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/settings.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/shims/claude.md.tmpl +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/shims/generic.md.tmpl +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/spinner.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/toml_check.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/__init__.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/logo.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/menu.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/model.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/picker.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/ui/theme.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/undo.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/update_check.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle/update_prompt.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle.egg-info/dependency_links.txt +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle.egg-info/entry_points.txt +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle.egg-info/requires.txt +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/agent_toggle.egg-info/top_level.txt +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/pyproject.toml +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/setup.cfg +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_all_type.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_cli.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_color.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_companions.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_conformance.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_containment.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_cost.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_doctor.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_encoding.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_fs.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_help.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_i18n.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_install.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_logo.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_menu.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_mods.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_picker.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_platform.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_profiles.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_project.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_safety.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_same_name.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_settings.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_store.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_theme.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_undo.py +0 -0
- {agent_toggle-0.12.0 → agent_toggle-0.13.0}/tests/test_update_check.py +0 -0
- {agent_toggle-0.12.0 → 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
|
|
182
|
+
```
|
|
183
|
+
|
|
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"'
|
|
181
187
|
```
|
|
182
188
|
|
|
183
|
-
|
|
184
|
-
`
|
|
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
|
|
@@ -426,8 +435,7 @@ end of input, `q` or an empty line exits `0`
|
|
|
426
435
|
| General | Check for updates, Color, Logo, Language, Default harness |
|
|
427
436
|
| Harnesses | one On/Off per harness, with `found ~/.x` or `not on this machine` |
|
|
428
437
|
| Picker | default sort, harness filter, type filter |
|
|
429
|
-
|
|
|
430
|
-
| 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 |
|
|
431
439
|
|
|
432
440
|
Keys: `↑` `↓` move (wraps, skips headings), `←` `→` cycle a value, `Enter` / `Space` change or run
|
|
433
441
|
the row, `r` reset the row, `R` reset everything (asks y/n), `q` / `Esc` / `Ctrl-C` quit. In an
|
|
@@ -566,7 +574,7 @@ rather than guessing.
|
|
|
566
574
|
|
|
567
575
|
OpenCode may read skills from another harness's directory through
|
|
568
576
|
`opencode.json` → `skills.paths` (absolute, `~`, `$HOME/...`
|
|
569
|
-
and `${HOME}/...` entries; read from `opencode.json
|
|
577
|
+
and `${HOME}/...` entries; read from `opencode.json` and `opencode.jsonc` (OpenCode loads both and merges them);
|
|
570
578
|
relative entries are skipped, since OpenCode resolves them against the session
|
|
571
579
|
directory). OpenCode also always scans `~/.claude/skills` and `~/.agents/skills`,
|
|
572
580
|
so claude's skills count as shared with it. Such a directory is **one** item, filed under
|
|
@@ -574,6 +582,11 @@ its owner (the harness whose home really holds it): it is
|
|
|
574
582
|
parked once, tracked once, and every row reports `shared_with`, the other
|
|
575
583
|
harnesses it also affects. `status` prints `shared dir with: ...`.
|
|
576
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
|
+
|
|
577
590
|
If the live directory carries `.synced-from-*` markers, a sync job may
|
|
578
591
|
re-create what you parked; `status` and `disable` warn about it. Park in the
|
|
579
592
|
source harness instead. The warning is printed once per real directory, naming
|
|
@@ -873,10 +886,11 @@ a warning and a `recovered` log row (which `undo` does not reverse). `status` an
|
|
|
873
886
|
`doctor` report it, with `agent-toggle enable ...` when the item ends up
|
|
874
887
|
disabled. The one case left to you is a cross-filesystem move killed between its
|
|
875
888
|
two renames: both copies are complete, and the report names the `diff -r` to
|
|
876
|
-
check and the `rm -rf` that keeps either one. MCP
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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.
|
|
880
894
|
A killed run also leaves its `lock`; the next run's error says so.
|
|
881
895
|
|
|
882
896
|
Each `log.jsonl` row is `{ts, harness, type, name, action, result, batch,
|
|
@@ -1027,21 +1041,24 @@ it on macOS, Linux and Windows.
|
|
|
1027
1041
|
|
|
1028
1042
|
## CI notifications
|
|
1029
1043
|
|
|
1030
|
-
|
|
1031
|
-
push (a lint-only failure does not page).
|
|
1032
|
-
|
|
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.
|
|
1033
1049
|
|
|
1034
|
-
|
|
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).
|
|
1035
1055
|
|
|
1036
1056
|
```sh
|
|
1037
|
-
agent-toggle config # settings menu: Notifications has the bot token (hidden) and chat id
|
|
1038
1057
|
agent-toggle config test # send one test message
|
|
1039
1058
|
agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on the repo
|
|
1040
1059
|
```
|
|
1041
1060
|
|
|
1042
|
-
|
|
1043
|
-
`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`). The bot token
|
|
1061
|
+
When the rows are shown, the bot token
|
|
1045
1062
|
is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
|
|
1046
1063
|
through telegram-kit, never in a file or on a command line, and is shown masked. With no
|
|
1047
1064
|
credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
|
|
@@ -1057,6 +1074,12 @@ gh secret set TELEGRAM_BOT_TOKEN
|
|
|
1057
1074
|
gh secret set TELEGRAM_CHAT_ID
|
|
1058
1075
|
```
|
|
1059
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
|
+
|
|
1060
1083
|
With either secret unset (a fork, or a clone you have not configured) the notify job
|
|
1061
1084
|
prints a `::notice::` and exits 0, so it never adds a second red X.
|
|
1062
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
|
|
149
|
+
```
|
|
150
|
+
|
|
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"'
|
|
148
154
|
```
|
|
149
155
|
|
|
150
|
-
|
|
151
|
-
`
|
|
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
|
|
@@ -393,8 +402,7 @@ end of input, `q` or an empty line exits `0`
|
|
|
393
402
|
| General | Check for updates, Color, Logo, Language, Default harness |
|
|
394
403
|
| Harnesses | one On/Off per harness, with `found ~/.x` or `not on this machine` |
|
|
395
404
|
| Picker | default sort, harness filter, type filter |
|
|
396
|
-
|
|
|
397
|
-
| 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 |
|
|
398
406
|
|
|
399
407
|
Keys: `↑` `↓` move (wraps, skips headings), `←` `→` cycle a value, `Enter` / `Space` change or run
|
|
400
408
|
the row, `r` reset the row, `R` reset everything (asks y/n), `q` / `Esc` / `Ctrl-C` quit. In an
|
|
@@ -533,7 +541,7 @@ rather than guessing.
|
|
|
533
541
|
|
|
534
542
|
OpenCode may read skills from another harness's directory through
|
|
535
543
|
`opencode.json` → `skills.paths` (absolute, `~`, `$HOME/...`
|
|
536
|
-
and `${HOME}/...` entries; read from `opencode.json
|
|
544
|
+
and `${HOME}/...` entries; read from `opencode.json` and `opencode.jsonc` (OpenCode loads both and merges them);
|
|
537
545
|
relative entries are skipped, since OpenCode resolves them against the session
|
|
538
546
|
directory). OpenCode also always scans `~/.claude/skills` and `~/.agents/skills`,
|
|
539
547
|
so claude's skills count as shared with it. Such a directory is **one** item, filed under
|
|
@@ -541,6 +549,11 @@ its owner (the harness whose home really holds it): it is
|
|
|
541
549
|
parked once, tracked once, and every row reports `shared_with`, the other
|
|
542
550
|
harnesses it also affects. `status` prints `shared dir with: ...`.
|
|
543
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
|
+
|
|
544
557
|
If the live directory carries `.synced-from-*` markers, a sync job may
|
|
545
558
|
re-create what you parked; `status` and `disable` warn about it. Park in the
|
|
546
559
|
source harness instead. The warning is printed once per real directory, naming
|
|
@@ -840,10 +853,11 @@ a warning and a `recovered` log row (which `undo` does not reverse). `status` an
|
|
|
840
853
|
`doctor` report it, with `agent-toggle enable ...` when the item ends up
|
|
841
854
|
disabled. The one case left to you is a cross-filesystem move killed between its
|
|
842
855
|
two renames: both copies are complete, and the report names the `diff -r` to
|
|
843
|
-
check and the `rm -rf` that keeps either one. MCP
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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.
|
|
847
861
|
A killed run also leaves its `lock`; the next run's error says so.
|
|
848
862
|
|
|
849
863
|
Each `log.jsonl` row is `{ts, harness, type, name, action, result, batch,
|
|
@@ -994,21 +1008,24 @@ it on macOS, Linux and Windows.
|
|
|
994
1008
|
|
|
995
1009
|
## CI notifications
|
|
996
1010
|
|
|
997
|
-
|
|
998
|
-
push (a lint-only failure does not page).
|
|
999
|
-
|
|
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.
|
|
1000
1016
|
|
|
1001
|
-
|
|
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).
|
|
1002
1022
|
|
|
1003
1023
|
```sh
|
|
1004
|
-
agent-toggle config # settings menu: Notifications has the bot token (hidden) and chat id
|
|
1005
1024
|
agent-toggle config test # send one test message
|
|
1006
1025
|
agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on the repo
|
|
1007
1026
|
```
|
|
1008
1027
|
|
|
1009
|
-
|
|
1010
|
-
`Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
|
|
1011
|
-
secrets (the same as `config test` / `config sync-ci`). The bot token
|
|
1028
|
+
When the rows are shown, the bot token
|
|
1012
1029
|
is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
|
|
1013
1030
|
through telegram-kit, never in a file or on a command line, and is shown masked. With no
|
|
1014
1031
|
credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
|
|
@@ -1024,6 +1041,12 @@ gh secret set TELEGRAM_BOT_TOKEN
|
|
|
1024
1041
|
gh secret set TELEGRAM_CHAT_ID
|
|
1025
1042
|
```
|
|
1026
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
|
+
|
|
1027
1050
|
With either secret unset (a fork, or a clone you have not configured) the notify job
|
|
1028
1051
|
prints a `::notice::` and exits 0, so it never adds a second red X.
|
|
1029
1052
|
|
|
@@ -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 =
|
|
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:
|
|
@@ -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:
|