agent-toggle 0.14.0__tar.gz → 0.16.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/PKG-INFO +35 -9
  2. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/README.md +33 -8
  3. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/__init__.py +1 -1
  4. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/cli.py +3 -3
  5. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/help.py +8 -6
  6. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/locales/zh_tw_cli.py +4 -3
  7. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/shims/claude.md.tmpl +1 -1
  8. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/shims/generic.md.tmpl +1 -1
  9. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/update_prompt.py +63 -45
  10. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle.egg-info/PKG-INFO +35 -9
  11. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle.egg-info/requires.txt +1 -0
  12. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/pyproject.toml +1 -1
  13. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_cli.py +55 -17
  14. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_cli_surface.py +7 -1
  15. agent_toggle-0.16.0/tests/test_update_prompt.py +162 -0
  16. agent_toggle-0.14.0/tests/test_update_prompt.py +0 -90
  17. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/LICENSE +0 -0
  18. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/__main__.py +0 -0
  19. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/backends/__init__.py +0 -0
  20. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/backends/flag_json.py +0 -0
  21. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/backends/mcp_json.py +0 -0
  22. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/backends/mcp_toml.py +0 -0
  23. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/backends/plugin_cli.py +0 -0
  24. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/companions.py +0 -0
  25. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/completions.py +0 -0
  26. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/config.py +0 -0
  27. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/cost.py +0 -0
  28. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/doctor.py +0 -0
  29. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/fs.py +0 -0
  30. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/harnesses.py +0 -0
  31. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/i18n.py +0 -0
  32. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/locales/__init__.py +0 -0
  33. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/locales/zh_tw_common.py +0 -0
  34. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/locales/zh_tw_config.py +0 -0
  35. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/locales/zh_tw_picker.py +0 -0
  36. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/mechanisms.py +0 -0
  37. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ops.py +0 -0
  38. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/output.py +0 -0
  39. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/profiles.py +0 -0
  40. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/settings.py +0 -0
  41. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/spinner.py +0 -0
  42. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/store.py +0 -0
  43. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/toml_check.py +0 -0
  44. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/__init__.py +0 -0
  45. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/config_menu.py +0 -0
  46. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/logo.py +0 -0
  47. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/menu.py +0 -0
  48. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/model.py +0 -0
  49. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/picker.py +0 -0
  50. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/ui/theme.py +0 -0
  51. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/undo.py +0 -0
  52. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle/update_check.py +0 -0
  53. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle.egg-info/SOURCES.txt +0 -0
  54. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle.egg-info/dependency_links.txt +0 -0
  55. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle.egg-info/entry_points.txt +0 -0
  56. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/agent_toggle.egg-info/top_level.txt +0 -0
  57. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/setup.cfg +0 -0
  58. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_alias.py +0 -0
  59. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_all_type.py +0 -0
  60. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_backends.py +0 -0
  61. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_color.py +0 -0
  62. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_companions.py +0 -0
  63. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_completions.py +0 -0
  64. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_config.py +0 -0
  65. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_config_menu.py +0 -0
  66. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_conformance.py +0 -0
  67. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_containment.py +0 -0
  68. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_cost.py +0 -0
  69. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_crash.py +0 -0
  70. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_doctor.py +0 -0
  71. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_encoding.py +0 -0
  72. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_fs.py +0 -0
  73. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_harnesses.py +0 -0
  74. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_help.py +0 -0
  75. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_i18n.py +0 -0
  76. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_install.py +0 -0
  77. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_logo.py +0 -0
  78. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_mechanisms.py +0 -0
  79. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_menu.py +0 -0
  80. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_mods.py +0 -0
  81. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_picker.py +0 -0
  82. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_platform.py +0 -0
  83. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_profiles.py +0 -0
  84. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_project.py +0 -0
  85. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_safety.py +0 -0
  86. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_same_name.py +0 -0
  87. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_secret_audit.py +0 -0
  88. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_settings.py +0 -0
  89. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_store.py +0 -0
  90. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_theme.py +0 -0
  91. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_undo.py +0 -0
  92. {agent_toggle-0.14.0 → agent_toggle-0.16.0}/tests/test_update_check.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-toggle
3
- Version: 0.14.0
3
+ Version: 0.16.0
4
4
  Summary: Temporarily disable / re-enable AI-agent skills, agents, commands, rules, plugins and MCP servers across harnesses (Claude Code, Codex, Grok, OpenCode, OpenClaw, Copilot, Vibe, Devin, Antigravity)
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/weskao/agent-toggle
@@ -25,6 +25,7 @@ Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: telegram-kit<0.3,>=0.2.2
28
+ Requires-Dist: update-kit<0.3,>=0.2.1
28
29
  Provides-Extra: windows
29
30
  Requires-Dist: windows-curses; sys_platform == "win32" and extra == "windows"
30
31
  Provides-Extra: telegram
@@ -40,6 +41,10 @@ rules, plugins (mods included), MCP servers — across Claude Code, Codex, Grok
40
41
  OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at
41
42
  session start.
42
43
 
44
+ It ships in two forms: a **CLI** (`agent-toggle`, all harnesses) and a **Claude Code mod**
45
+ (the `/agent-toggle` pane, claude harness only; see [Claude Code pane mod](#claude-code-pane-mod)).
46
+ Both drive the same toggle engine and state.
47
+
43
48
  **Nothing is ever deleted.** Everything is parked and recorded, and `enable`
44
49
  puts it back where it came from.
45
50
 
@@ -202,7 +207,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
202
207
  | `status` | health check: harnesses found, types each supports, parked counts, gitignore, untracked parked items, stale live twins, shared dirs, and state entries whose parked item is gone (a `stale` row with the fix command; read-only, still exit `0`) |
203
208
  | `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
204
209
  | `cost [--type T]` | estimated startup tokens per item, biggest first (read-only; `--harness H` filters; `--project <dir>` prices a repo's `.claude/` and `.mcp.json` instead of user scope) |
205
- | `update` | check PyPI now (ignores the cache and a skipped version) and print the commands that update the CLI, the Claude Code plugin (when installed) and the shims; runs none of them. `/agent-toggle update` in Claude Code does the same; opening the pane toasts a newer release once |
210
+ | `update` (also `--update`) | install the newest release tag (`uv tool install --force --no-sources git+<repo>@<tag>`, or `pipx install --force` for a pipx install), then, with the new version, update the Claude Code plugin (when installed) and the shims. Never asks. Always the newest tag: a dev checkout, a branch or an unmanaged install is replaced by it. Exit `0` updated or already current, `1` the check or install failed (offline, rate limit), `2` neither uv nor pipx is found (the manual command is printed). Messages go to stderr; `--json` has no JSON for it. The `/agent-toggle` pane command has no `update`: run it in a terminal |
206
211
  | `install-shims` | write the skill shim into every installed harness that is on in the settings (an off one is skipped unless named with `--harness`); refuses to overwrite a file it did not write (`--dry-run` shows the plan); reports stale legacy `<dir>-disabled/` lines in a harness-home `.gitignore` (`--gitignore` removes them, `--no-gitignore` skips the check); on a terminal, then asks y/n for each problem `doctor` can fix (see [Fixing](#fixing)) |
207
212
  | `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
208
213
  | `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
@@ -495,18 +500,17 @@ invalid value in either is ignored with one warning on stderr.
495
500
 
496
501
  On every invocation (unless disabled) agent-toggle checks PyPI for a newer release. It starts
497
502
  before the command and is offered after it, on every exit path; it overlaps the command and waits at most 0.8 s afterwards.
503
+ `update` and `completion` skip it.
498
504
 
499
505
  - **On a terminal** (stdin and stderr are TTYs, no `--json`): a panel on stderr, "agent-toggle X
500
506
  is available (you have Y)", with `Update now`, `Skip` and `Skip until next version`, a release
501
- notes link, and `↑` `↓` `Enter` `q`. `Update now` runs, in order and stopping at the first
502
- failure: `uv tool upgrade agent-toggle` (`pipx upgrade agent-toggle` for a pipx install), then,
503
- when the Claude Code plugin is installed, `claude plugin marketplace update agent-toggle` and
504
- `claude plugin update agent-toggle@agent-toggle`, then `agent-toggle install-shims`. A failure
505
- gives a yellow warning listing the commands to run yourself.
507
+ notes link, and `↑` `↓` `Enter` `q`. `Update now` runs [`agent-toggle update`](#update) (the
508
+ newest release tag, then the Claude Code plugin and the shims). A failure gives a yellow warning
509
+ naming the command to run yourself.
506
510
  - **Windows**: a running `agent-toggle.exe` cannot be replaced, so there is no `Update now`; the
507
- commands are printed instead (as below), to run once agent-toggle has exited.
511
+ command is printed instead (as below), to run once agent-toggle has exited.
508
512
  - **Off a terminal** (or `--json`): a stderr line, `agent-toggle X is available (you have Y)`,
509
- then those commands, one per line.
513
+ then ` agent-toggle update`.
510
514
  - It never changes the exit code or stdout, so `--json` output stays one document.
511
515
 
512
516
  It is one `GET https://pypi.org/pypi/agent-toggle/json` on a background thread with a 0.8 s
@@ -515,6 +519,28 @@ normal HTTP request with the User-Agent `agent-toggle-update-check`. See `SECURI
515
519
  for the hardening. To turn it off, set `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting
516
520
  to off (the settings menu's "Check for updates").
517
521
 
522
+ ## Update
523
+
524
+ `agent-toggle update` (or `agent-toggle --update`) is a self-update through
525
+ update-kit (a small stdlib-only helper library): it asks the repository for its newest `vX.Y.Z`
526
+ tag (`git ls-remote --tags`) and always installs that tag with
527
+ `uv tool install --force --no-sources git+<repo>@<tag>` (`pipx install --force` for a pipx install).
528
+ A dev checkout, a branch or commit install, or an unmanaged install is replaced by the tag too
529
+ (with uv, else pipx); only an install made from exactly that tag is already up to date.
530
+ It then runs, with the new binary and in order: when the Claude Code plugin is installed,
531
+ `claude plugin marketplace update agent-toggle` and `claude plugin update agent-toggle@agent-toggle`,
532
+ then `agent-toggle install-shims`. A failing step prints a warning and makes the exit code `1`.
533
+ It never prompts and writes to stderr only.
534
+
535
+ | Exit | Meaning |
536
+ |---|---|
537
+ | `0` | updated, or already up to date |
538
+ | `1` | could not check (offline, rate limit) or the install failed |
539
+ | `2` | neither uv nor pipx is found (the manual command is printed) |
540
+
541
+ On Windows a running `agent-toggle.exe` cannot be replaced, so the install runs detached after
542
+ agent-toggle exits (log in `%USERPROFILE%\.update-kit\`).
543
+
518
544
  ## Help
519
545
 
520
546
  ```sh
@@ -7,6 +7,10 @@ rules, plugins (mods included), MCP servers — across Claude Code, Codex, Grok
7
7
  OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at
8
8
  session start.
9
9
 
10
+ It ships in two forms: a **CLI** (`agent-toggle`, all harnesses) and a **Claude Code mod**
11
+ (the `/agent-toggle` pane, claude harness only; see [Claude Code pane mod](#claude-code-pane-mod)).
12
+ Both drive the same toggle engine and state.
13
+
10
14
  **Nothing is ever deleted.** Everything is parked and recorded, and `enable`
11
15
  puts it back where it came from.
12
16
 
@@ -169,7 +173,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
169
173
  | `status` | health check: harnesses found, types each supports, parked counts, gitignore, untracked parked items, stale live twins, shared dirs, and state entries whose parked item is gone (a `stale` row with the fix command; read-only, still exit `0`) |
170
174
  | `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
171
175
  | `cost [--type T]` | estimated startup tokens per item, biggest first (read-only; `--harness H` filters; `--project <dir>` prices a repo's `.claude/` and `.mcp.json` instead of user scope) |
172
- | `update` | check PyPI now (ignores the cache and a skipped version) and print the commands that update the CLI, the Claude Code plugin (when installed) and the shims; runs none of them. `/agent-toggle update` in Claude Code does the same; opening the pane toasts a newer release once |
176
+ | `update` (also `--update`) | install the newest release tag (`uv tool install --force --no-sources git+<repo>@<tag>`, or `pipx install --force` for a pipx install), then, with the new version, update the Claude Code plugin (when installed) and the shims. Never asks. Always the newest tag: a dev checkout, a branch or an unmanaged install is replaced by it. Exit `0` updated or already current, `1` the check or install failed (offline, rate limit), `2` neither uv nor pipx is found (the manual command is printed). Messages go to stderr; `--json` has no JSON for it. The `/agent-toggle` pane command has no `update`: run it in a terminal |
173
177
  | `install-shims` | write the skill shim into every installed harness that is on in the settings (an off one is skipped unless named with `--harness`); refuses to overwrite a file it did not write (`--dry-run` shows the plan); reports stale legacy `<dir>-disabled/` lines in a harness-home `.gitignore` (`--gitignore` removes them, `--no-gitignore` skips the check); on a terminal, then asks y/n for each problem `doctor` can fix (see [Fixing](#fixing)) |
174
178
  | `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
175
179
  | `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
@@ -462,18 +466,17 @@ invalid value in either is ignored with one warning on stderr.
462
466
 
463
467
  On every invocation (unless disabled) agent-toggle checks PyPI for a newer release. It starts
464
468
  before the command and is offered after it, on every exit path; it overlaps the command and waits at most 0.8 s afterwards.
469
+ `update` and `completion` skip it.
465
470
 
466
471
  - **On a terminal** (stdin and stderr are TTYs, no `--json`): a panel on stderr, "agent-toggle X
467
472
  is available (you have Y)", with `Update now`, `Skip` and `Skip until next version`, a release
468
- notes link, and `↑` `↓` `Enter` `q`. `Update now` runs, in order and stopping at the first
469
- failure: `uv tool upgrade agent-toggle` (`pipx upgrade agent-toggle` for a pipx install), then,
470
- when the Claude Code plugin is installed, `claude plugin marketplace update agent-toggle` and
471
- `claude plugin update agent-toggle@agent-toggle`, then `agent-toggle install-shims`. A failure
472
- gives a yellow warning listing the commands to run yourself.
473
+ notes link, and `↑` `↓` `Enter` `q`. `Update now` runs [`agent-toggle update`](#update) (the
474
+ newest release tag, then the Claude Code plugin and the shims). A failure gives a yellow warning
475
+ naming the command to run yourself.
473
476
  - **Windows**: a running `agent-toggle.exe` cannot be replaced, so there is no `Update now`; the
474
- commands are printed instead (as below), to run once agent-toggle has exited.
477
+ command is printed instead (as below), to run once agent-toggle has exited.
475
478
  - **Off a terminal** (or `--json`): a stderr line, `agent-toggle X is available (you have Y)`,
476
- then those commands, one per line.
479
+ then ` agent-toggle update`.
477
480
  - It never changes the exit code or stdout, so `--json` output stays one document.
478
481
 
479
482
  It is one `GET https://pypi.org/pypi/agent-toggle/json` on a background thread with a 0.8 s
@@ -482,6 +485,28 @@ normal HTTP request with the User-Agent `agent-toggle-update-check`. See `SECURI
482
485
  for the hardening. To turn it off, set `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting
483
486
  to off (the settings menu's "Check for updates").
484
487
 
488
+ ## Update
489
+
490
+ `agent-toggle update` (or `agent-toggle --update`) is a self-update through
491
+ update-kit (a small stdlib-only helper library): it asks the repository for its newest `vX.Y.Z`
492
+ tag (`git ls-remote --tags`) and always installs that tag with
493
+ `uv tool install --force --no-sources git+<repo>@<tag>` (`pipx install --force` for a pipx install).
494
+ A dev checkout, a branch or commit install, or an unmanaged install is replaced by the tag too
495
+ (with uv, else pipx); only an install made from exactly that tag is already up to date.
496
+ It then runs, with the new binary and in order: when the Claude Code plugin is installed,
497
+ `claude plugin marketplace update agent-toggle` and `claude plugin update agent-toggle@agent-toggle`,
498
+ then `agent-toggle install-shims`. A failing step prints a warning and makes the exit code `1`.
499
+ It never prompts and writes to stderr only.
500
+
501
+ | Exit | Meaning |
502
+ |---|---|
503
+ | `0` | updated, or already up to date |
504
+ | `1` | could not check (offline, rate limit) or the install failed |
505
+ | `2` | neither uv nor pipx is found (the manual command is printed) |
506
+
507
+ On Windows a running `agent-toggle.exe` cannot be replaced, so the install runs detached after
508
+ agent-toggle exits (log in `%USERPROFILE%\.update-kit\`).
509
+
485
510
  ## Help
486
511
 
487
512
  ```sh
@@ -1,3 +1,3 @@
1
1
  """agent-toggle: temporarily disable / re-enable AI-agent resources."""
2
2
 
3
- __version__ = "0.14.0"
3
+ __version__ = "0.16.0"
@@ -695,7 +695,7 @@ def build_parser() -> argparse.ArgumentParser:
695
695
  sub.add_parser("doctor", parents=[common],
696
696
  help="check harness layouts and state against disk; offers y/n fixes")
697
697
  sub.add_parser("update", parents=[common],
698
- help="check for a newer release; show the commands that update everything")
698
+ help="install the newest release, then refresh the plugin and shims")
699
699
  cf = sub.add_parser("config", parents=[common],
700
700
  help="Telegram settings for the CI failure alerts")
701
701
  cf.add_argument("action", nargs="?", choices=("test", "sync-ci"),
@@ -734,7 +734,7 @@ def main(argv: list[str] | None = None) -> int:
734
734
  i18n.set_language(language)
735
735
  color = scan_color(argv, color) # --color > env > setting > auto
736
736
  i = _command_index(argv)
737
- # `update` is the check; `completion` may run on every shell start (eval "$(...)")
737
+ # `update` is the update; `completion` may run on every shell start (eval "$(...)")
738
738
  started = None if argv[i:i + 1] in (["update"], ["completion"]) else update_prompt.start()
739
739
  try:
740
740
  return _main(argv, color)
@@ -804,7 +804,7 @@ def _main(argv: list[str], color: str) -> int:
804
804
  if fixes and _interactive(out):
805
805
  doctor.offer_fixes(fixes, out)
806
806
  elif cmd == "update":
807
- update_prompt.cmd_update(out)
807
+ return update_prompt.cmd_update()
808
808
  elif cmd == "config":
809
809
  config.cmd_config(args, out)
810
810
  elif cmd == "completion":
@@ -137,12 +137,14 @@ def commands(default_harness: str = "claude") -> dict[str, Cmd]:
137
137
  examples=["config", "config test", "config sync-ci --dry-run"]),
138
138
  "update": Cmd(
139
139
  "update",
140
- t("help.update.summary", "check for a newer release; show how to update"),
141
- t("help.update.desc", "Ask PyPI now (ignoring the cache and a skipped version) and, "
142
- "when a newer release exists, print the commands that update "
143
- "the CLI, the Claude Code plugin (if installed) and the shims. "
144
- "Runs none of them."),
145
- examples=["update", "update --json"]),
140
+ t("help.update.summary", "install the newest release; refresh the plugin and shims"),
141
+ t("help.update.desc", "Install the newest release tag (uv tool or pipx), then, with the "
142
+ "new version, update the Claude Code plugin (if installed) and "
143
+ "reinstall the shims. Never asks; `--update` is the same. Always "
144
+ "the newest tag: a dev or unmanaged install is replaced by it. "
145
+ "Exit 1 when the check or the install fails, 2 when neither uv "
146
+ "nor pipx is found."),
147
+ examples=["update"]),
146
148
  "completion": Cmd(
147
149
  "completion bash|zsh|fish|powershell",
148
150
  t("help.completion.summary", "print the shell completion script"),
@@ -27,9 +27,10 @@ CATALOG: dict[str, str] = {
27
27
  "help.cost.summary": "每個項目的預估啟動 token 數,由大到小",
28
28
  "help.cost.desc": "估算每個項目啟動時載入多少 token(字元數/4,±25%),"
29
29
  "以及暫存項目已省下多少。",
30
- "help.update.summary": "檢查新版本;顯示更新方式",
31
- "help.update.desc": "立即向 PyPI 查詢(忽略快取與已略過的版本);有新版本時,印出更新 CLI、"
32
- "Claude Code 外掛(若已安裝)與 shim 的指令。不會執行任何指令。",
30
+ "help.update.summary": "安裝最新版本,並更新外掛與 shim",
31
+ "help.update.desc": "安裝最新的發行標籤(uv tool / pipx),完成後以新版更新 Claude Code 外掛(若已安裝)"
32
+ "並重新安裝 shim。不會提問;`--update` 等同 `update`。一律安裝最新標籤:開發版或非受管安裝也會被取代。"
33
+ "檢查或安裝失敗時結束碼 1;找不到 uv 與 pipx 時結束碼 2。",
33
34
  "help.completion.summary": "印出 shell 自動補完腳本",
34
35
  "help.completion.desc": "在 stdout 印出某個 shell 的 Tab 補完腳本;"
35
36
  "存到該 shell 載入補完的位置(見 README「Shell completion」)。",
@@ -17,7 +17,7 @@ agent-toggle <command> --json
17
17
  ```
18
18
  If `agent-toggle` is not on PATH, ask the user to install it (`uv tool install agent-toggle`); do not guess where a checkout lives.
19
19
 
20
- Commands: `status` (run first), `doctor` (read-only health check), `update` (checks for a newer release and lists the commands that update it; run none yourself, relay them), `list [type]`,
20
+ Commands: `status` (run first), `doctor` (read-only health check), `update` (installs the newest release, then refreshes the plugin and shims; run it only when the user asks to update), `list [type]`,
21
21
  `cost [--type T]` (startup token estimates, biggest first), `disable <type> <name>...` (type `all`: every type with that name),
22
22
  `enable <type> <name>...`, `enable --all`, `undo` (reverse the last batch),
23
23
  `profile save|apply|diff|delete <name>`, `profile rename <old> <new>`, `profile list` (see Profiles), `migrate`; add `--harness claude|codex|grok|opencode|openclaw|copilot|vibe|devin|agy`
@@ -20,7 +20,7 @@ You are running in `__HARNESS__`: pass `--harness __HARNESS__` for its own items
20
20
  (the default is `claude`). Other harnesses: `--harness claude|codex|grok|opencode|openclaw|copilot|vibe|devin|agy`.
21
21
 
22
22
  Commands: `status` (run first; shows which types this harness supports),
23
- `doctor` (read-only health check), `update` (checks for a newer release and lists the commands that update it; run none yourself, relay them), `list [type]`, `cost [--type T]` (startup
23
+ `doctor` (read-only health check), `update` (installs the newest release, then refreshes the plugin and shims; run it only when the user asks to update), `list [type]`, `cost [--type T]` (startup
24
24
  token estimates, biggest first), `disable <type> <name>...` (type `all`: every type with that name), `enable <type> <name>...`,
25
25
  `enable --all`, `undo` (reverse the last batch), `profile save|apply|diff|delete <name>`,
26
26
  `profile rename <old> <new>`, `profile list` (see Profiles); add `--dry-run` to preview. `<type>` = `skill` / `agent` /
@@ -1,12 +1,15 @@
1
- """The update check's UI: start it before a command, offer the update after it.
1
+ """The update check's UI, and `update` / `--update`.
2
2
 
3
3
  started = start() # None when update_check is off; never raises
4
4
  ... # the command
5
5
  offer(started, json_mode=..., color=...) # never raises, never touches stdout
6
6
 
7
+ cmd_update() # install the newest tag (update_kit), then the plugin and shims
8
+
7
9
  On a terminal (stdin AND stderr are TTYs) and not under --json, :func:`ask` shows
8
10
  a small arrow-key panel on stderr; elsewhere :func:`hint` prints two plain lines
9
- on stderr. What each answer does lives in :func:`update_check.offer`.
11
+ on stderr. What each answer does lives in :func:`update_check.offer`; Update now
12
+ is :func:`cmd_update`.
10
13
  """
11
14
  from __future__ import annotations
12
15
 
@@ -14,20 +17,20 @@ import os
14
17
  import re
15
18
  import subprocess
16
19
  import sys
17
- from collections.abc import Iterable, Iterator
20
+ from collections.abc import Iterable, Iterator, Sequence
21
+ from functools import cached_property
18
22
  from pathlib import Path
19
23
 
20
- from . import __version__, fs, settings, update_check
24
+ from . import __version__, fs, i18n, settings, update_check
21
25
  from .backends import plugin_cli
22
26
  from .i18n import t
23
27
  from .output import use_color
24
28
  from .ui import theme
25
29
 
26
30
  DIST = "agent-toggle"
31
+ REPO = "weskao/agent-toggle"
27
32
  PLUGIN = "agent-toggle@agent-toggle"
28
- # shortcut: pipx is spotted by "pipx" in the venv path (its default homes); a custom PIPX_HOME gets uv
29
- UPGRADE = (["pipx", "upgrade", DIST] if "pipx" in Path(sys.prefix).parts
30
- else ["uv", "tool", "upgrade", DIST])
33
+ UPDATE = [DIST, "update"]
31
34
  RELEASE_NOTES = "https://github.com/weskao/agent-toggle/releases/tag/v%s"
32
35
  ANSWERS = (update_check.UPDATE_NOW, update_check.SKIP, update_check.SKIP_VERSION)
33
36
 
@@ -40,16 +43,51 @@ def _clean(text: str) -> str:
40
43
  return _CONTROL.sub("", text)
41
44
 
42
45
 
43
- def upgrade_steps() -> list[list[str]]:
44
- """Upgrade the CLI, then the Claude Code plugin when it is installed, then refresh the shims."""
45
- steps = [UPGRADE]
46
- # bare "claude": what a person pastes; offer()'s run() resolves it, as claude is often off PATH
46
+ def after_steps() -> list[list[str]]:
47
+ """Run with the NEW binary once the install is done: the Claude Code plugin when it is
48
+ installed, then the shims."""
49
+ steps: list[list[str]] = []
50
+ # bare "claude": what a person pastes; _run() resolves it, as claude is often off PATH
47
51
  if plugin_cli.claude_bin() and any(p["id"] == PLUGIN for p in plugin_cli.list_plugins(lambda _msg: None)):
48
52
  steps += [["claude", "plugin", "marketplace", "update", DIST],
49
53
  ["claude", "plugin", "update", PLUGIN]]
50
54
  return [*steps, [DIST, "install-shims"]]
51
55
 
52
56
 
57
+ class _LazySteps(Sequence):
58
+ """after_steps(), computed on first use: update_kit only reads `after` once an install
59
+ will happen, so a failed check / "already the latest" skips `claude plugin list` (0.8 s)."""
60
+
61
+ @cached_property
62
+ def _steps(self) -> list[list[str]]:
63
+ return after_steps()
64
+
65
+ def __len__(self) -> int:
66
+ return len(self._steps)
67
+
68
+ def __getitem__(self, i):
69
+ return self._steps[i]
70
+
71
+
72
+ def _run(cmd, **kw):
73
+ """update_kit's subprocess runner: their output stays off stdout and no step may prompt,
74
+ and a step like `install-shims` must not re-check for updates and print the hint."""
75
+ if cmd[0] == "claude":
76
+ cmd = [plugin_cli.claude_bin() or "claude", *cmd[1:]]
77
+ # update_kit passes its own env (no-prompt git vars) for the install step: merge, don't replace
78
+ kw["env"] = {**kw.get("env", os.environ), "AGENT_TOGGLE_UPDATE_CHECK": "0"}
79
+ return subprocess.run(cmd, stdout=sys.stderr, stdin=subprocess.DEVNULL, **kw)
80
+
81
+
82
+ def cmd_update() -> int:
83
+ """`update` / `--update` and the panel's Update now: install the newest release tag, then
84
+ refresh the plugin and shims. Returns update_kit's exit code."""
85
+ # imported here, like telegram_kit: a checkout run (`python3 agent_toggle.py`) or a stale env lacks it
86
+ import update_kit
87
+ return update_kit.update(app=DIST, repo=REPO, dist=DIST, lang=i18n.LANGUAGE,
88
+ after=_LazySteps(), out=sys.stderr, run=_run)
89
+
90
+
53
91
  def cache_path() -> Path:
54
92
  return fs.state_dir() / "update-check.json"
55
93
 
@@ -78,17 +116,16 @@ def _available(found: update_check.UpdateAvailable) -> str:
78
116
 
79
117
 
80
118
  def hint(found: update_check.UpdateAvailable) -> str:
81
- """The `ask` off a terminal (CI, pipes, --json): the headline, then one command per line."""
119
+ """The `ask` off a terminal (CI, pipes, --json): the headline, then the command to run."""
82
120
  print(_available(found), file=sys.stderr)
83
- for step in upgrade_steps():
84
- print(" " + " ".join(step), file=sys.stderr)
121
+ print(" " + " ".join(UPDATE), file=sys.stderr)
85
122
  return update_check.SKIP
86
123
 
87
124
 
88
125
  def _lines(found: update_check.UpdateAvailable, selected: int, color: bool,
89
126
  g: theme.Glyphs) -> list[str]:
90
127
  rows = (
91
- (t("update.now", "Update now"), " ".join(UPGRADE)),
128
+ (t("update.now", "Update now"), " ".join(UPDATE)),
92
129
  (t("update.skip", "Skip"), t("update.skip_detail", "ask again next run")),
93
130
  (t("update.skip_version", "Skip until next version"),
94
131
  t("update.skip_version_detail", "ask again once a newer version ships")),
@@ -197,46 +234,27 @@ def offer(started: update_check.Started | None, *, json_mode: bool = False,
197
234
  stderr only and never raises, so neither stdout nor the exit code can change."""
198
235
  if started is None:
199
236
  return None
200
- # Windows cannot replace the agent-toggle.exe that is running: print the commands, run none
237
+ # Windows cannot replace the agent-toggle.exe that is running: print the command, run none
201
238
  if is_interactive() and not json_mode and not fs.WIN:
202
239
  def asker(found):
203
240
  return ask(found, color_mode)
204
241
  else:
205
242
  asker = hint
206
- def run(cmd, **kw): # their output must never reach stdout; no step may prompt
207
- if cmd[0] == "claude":
208
- cmd = [plugin_cli.claude_bin() or "claude", *cmd[1:]]
209
- # the install-shims step is agent-toggle itself: it must not re-check and print the hint again
210
- env = {**os.environ, settings.DEFAULTS["update_check"].env: "0"}
211
- return subprocess.run(cmd, stdout=sys.stderr, stdin=subprocess.DEVNULL, env=env, **kw)
212
- steps: list[list[str]] = []
213
- def upgrade(_found):
214
- steps.extend(upgrade_steps())
215
- return steps
216
- answer = update_check.offer(started, asker, cache_path=cache_path(), upgrade=upgrade, run=run)
243
+ # no steps for update_check to run: Update now is cmd_update() (update_kit, newest tag)
244
+ answer = update_check.offer(started, asker, cache_path=cache_path(), upgrade=lambda _found: ())
245
+ if answer == update_check.UPDATE_NOW:
246
+ try:
247
+ failed = cmd_update() != 0
248
+ except Exception: # noqa: BLE001 - an update offer must never fail the command
249
+ failed = True
250
+ if failed:
251
+ answer = update_check.UPGRADE_FAILED
217
252
  if answer == update_check.UPGRADE_FAILED:
218
253
  try:
219
254
  mark = theme.ansi("warning", "⚠" if theme.get_glyphs(sys.stderr).unicode else "!",
220
255
  use_color(sys.stderr, color_mode))
221
256
  print(mark + " " + t("update.failed", "upgrade did not finish — run it yourself: %s",
222
- "\n ".join(map(" ".join, steps or [UPGRADE]))), file=sys.stderr)
257
+ " ".join(UPDATE)), file=sys.stderr)
223
258
  except Exception: # noqa: BLE001, S110 - a hint must never fail the command
224
259
  pass
225
260
  return answer
226
-
227
-
228
- def cmd_update(out) -> None:
229
- """`update`: check PyPI now (cache and "skipped" ignored), show what would be updated and
230
- the commands for it. Runs none of them: the picker's Update now does that."""
231
- found = update_check.check(DIST, __version__, cache_path=cache_path(), ttl_seconds=0,
232
- timeout=5.0, ignore_skip=True)
233
- if found is None:
234
- out.row(None, "cli", DIST, "update", "ok", f"no newer release found (you have {__version__})",
235
- current=__version__, latest=None, commands=[])
236
- return
237
- steps = [" ".join(s) for s in upgrade_steps()]
238
- out.row(None, "cli", DIST, "update", "ok", f"{found.current} -> {found.latest}", show=False,
239
- current=found.current, latest=found.latest, commands=steps)
240
- out.say(_available(found) + ". Run, in order:")
241
- for step in steps:
242
- out.say(" " + step)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-toggle
3
- Version: 0.14.0
3
+ Version: 0.16.0
4
4
  Summary: Temporarily disable / re-enable AI-agent skills, agents, commands, rules, plugins and MCP servers across harnesses (Claude Code, Codex, Grok, OpenCode, OpenClaw, Copilot, Vibe, Devin, Antigravity)
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/weskao/agent-toggle
@@ -25,6 +25,7 @@ Requires-Python: >=3.10
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE
27
27
  Requires-Dist: telegram-kit<0.3,>=0.2.2
28
+ Requires-Dist: update-kit<0.3,>=0.2.1
28
29
  Provides-Extra: windows
29
30
  Requires-Dist: windows-curses; sys_platform == "win32" and extra == "windows"
30
31
  Provides-Extra: telegram
@@ -40,6 +41,10 @@ rules, plugins (mods included), MCP servers — across Claude Code, Codex, Grok
40
41
  OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at
41
42
  session start.
42
43
 
44
+ It ships in two forms: a **CLI** (`agent-toggle`, all harnesses) and a **Claude Code mod**
45
+ (the `/agent-toggle` pane, claude harness only; see [Claude Code pane mod](#claude-code-pane-mod)).
46
+ Both drive the same toggle engine and state.
47
+
43
48
  **Nothing is ever deleted.** Everything is parked and recorded, and `enable`
44
49
  puts it back where it came from.
45
50
 
@@ -202,7 +207,7 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
202
207
  | `status` | health check: harnesses found, types each supports, parked counts, gitignore, untracked parked items, stale live twins, shared dirs, and state entries whose parked item is gone (a `stale` row with the fix command; read-only, still exit `0`) |
203
208
  | `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
204
209
  | `cost [--type T]` | estimated startup tokens per item, biggest first (read-only; `--harness H` filters; `--project <dir>` prices a repo's `.claude/` and `.mcp.json` instead of user scope) |
205
- | `update` | check PyPI now (ignores the cache and a skipped version) and print the commands that update the CLI, the Claude Code plugin (when installed) and the shims; runs none of them. `/agent-toggle update` in Claude Code does the same; opening the pane toasts a newer release once |
210
+ | `update` (also `--update`) | install the newest release tag (`uv tool install --force --no-sources git+<repo>@<tag>`, or `pipx install --force` for a pipx install), then, with the new version, update the Claude Code plugin (when installed) and the shims. Never asks. Always the newest tag: a dev checkout, a branch or an unmanaged install is replaced by it. Exit `0` updated or already current, `1` the check or install failed (offline, rate limit), `2` neither uv nor pipx is found (the manual command is printed). Messages go to stderr; `--json` has no JSON for it. The `/agent-toggle` pane command has no `update`: run it in a terminal |
206
211
  | `install-shims` | write the skill shim into every installed harness that is on in the settings (an off one is skipped unless named with `--harness`); refuses to overwrite a file it did not write (`--dry-run` shows the plan); reports stale legacy `<dir>-disabled/` lines in a harness-home `.gitignore` (`--gitignore` removes them, `--no-gitignore` skips the check); on a terminal, then asks y/n for each problem `doctor` can fix (see [Fixing](#fixing)) |
207
212
  | `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
208
213
  | `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
@@ -495,18 +500,17 @@ invalid value in either is ignored with one warning on stderr.
495
500
 
496
501
  On every invocation (unless disabled) agent-toggle checks PyPI for a newer release. It starts
497
502
  before the command and is offered after it, on every exit path; it overlaps the command and waits at most 0.8 s afterwards.
503
+ `update` and `completion` skip it.
498
504
 
499
505
  - **On a terminal** (stdin and stderr are TTYs, no `--json`): a panel on stderr, "agent-toggle X
500
506
  is available (you have Y)", with `Update now`, `Skip` and `Skip until next version`, a release
501
- notes link, and `↑` `↓` `Enter` `q`. `Update now` runs, in order and stopping at the first
502
- failure: `uv tool upgrade agent-toggle` (`pipx upgrade agent-toggle` for a pipx install), then,
503
- when the Claude Code plugin is installed, `claude plugin marketplace update agent-toggle` and
504
- `claude plugin update agent-toggle@agent-toggle`, then `agent-toggle install-shims`. A failure
505
- gives a yellow warning listing the commands to run yourself.
507
+ notes link, and `↑` `↓` `Enter` `q`. `Update now` runs [`agent-toggle update`](#update) (the
508
+ newest release tag, then the Claude Code plugin and the shims). A failure gives a yellow warning
509
+ naming the command to run yourself.
506
510
  - **Windows**: a running `agent-toggle.exe` cannot be replaced, so there is no `Update now`; the
507
- commands are printed instead (as below), to run once agent-toggle has exited.
511
+ command is printed instead (as below), to run once agent-toggle has exited.
508
512
  - **Off a terminal** (or `--json`): a stderr line, `agent-toggle X is available (you have Y)`,
509
- then those commands, one per line.
513
+ then ` agent-toggle update`.
510
514
  - It never changes the exit code or stdout, so `--json` output stays one document.
511
515
 
512
516
  It is one `GET https://pypi.org/pypi/agent-toggle/json` on a background thread with a 0.8 s
@@ -515,6 +519,28 @@ normal HTTP request with the User-Agent `agent-toggle-update-check`. See `SECURI
515
519
  for the hardening. To turn it off, set `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting
516
520
  to off (the settings menu's "Check for updates").
517
521
 
522
+ ## Update
523
+
524
+ `agent-toggle update` (or `agent-toggle --update`) is a self-update through
525
+ update-kit (a small stdlib-only helper library): it asks the repository for its newest `vX.Y.Z`
526
+ tag (`git ls-remote --tags`) and always installs that tag with
527
+ `uv tool install --force --no-sources git+<repo>@<tag>` (`pipx install --force` for a pipx install).
528
+ A dev checkout, a branch or commit install, or an unmanaged install is replaced by the tag too
529
+ (with uv, else pipx); only an install made from exactly that tag is already up to date.
530
+ It then runs, with the new binary and in order: when the Claude Code plugin is installed,
531
+ `claude plugin marketplace update agent-toggle` and `claude plugin update agent-toggle@agent-toggle`,
532
+ then `agent-toggle install-shims`. A failing step prints a warning and makes the exit code `1`.
533
+ It never prompts and writes to stderr only.
534
+
535
+ | Exit | Meaning |
536
+ |---|---|
537
+ | `0` | updated, or already up to date |
538
+ | `1` | could not check (offline, rate limit) or the install failed |
539
+ | `2` | neither uv nor pipx is found (the manual command is printed) |
540
+
541
+ On Windows a running `agent-toggle.exe` cannot be replaced, so the install runs detached after
542
+ agent-toggle exits (log in `%USERPROFILE%\.update-kit\`).
543
+
518
544
  ## Help
519
545
 
520
546
  ```sh
@@ -1,4 +1,5 @@
1
1
  telegram-kit<0.3,>=0.2.2
2
+ update-kit<0.3,>=0.2.1
2
3
 
3
4
  [telegram]
4
5
  telegram-kit<0.3,>=0.2.2
@@ -27,7 +27,7 @@ classifiers = [
27
27
  ]
28
28
  requires-python = ">=3.10"
29
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
+ dependencies = ["telegram-kit>=0.2.2,<0.3", "update-kit>=0.2.1,<0.3"]
31
31
 
32
32
  [project.urls]
33
33
  Homepage = "https://github.com/weskao/agent-toggle"