agent-toggle 0.1.0__tar.gz → 0.5.2__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.5.2/PKG-INFO +884 -0
- agent_toggle-0.5.2/README.md +852 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/__init__.py +1 -1
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/backends/flag_json.py +42 -12
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/backends/mcp_json.py +154 -18
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/backends/mcp_toml.py +1 -2
- agent_toggle-0.5.2/agent_toggle/backends/plugin_cli.py +90 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/cli.py +317 -64
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/companions.py +6 -1
- agent_toggle-0.5.2/agent_toggle/config.py +136 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/cost.py +46 -27
- agent_toggle-0.5.2/agent_toggle/doctor.py +546 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/fs.py +149 -11
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/harnesses.py +56 -24
- agent_toggle-0.5.2/agent_toggle/help.py +224 -0
- agent_toggle-0.5.2/agent_toggle/i18n.py +69 -0
- agent_toggle-0.5.2/agent_toggle/locales/__init__.py +5 -0
- agent_toggle-0.5.2/agent_toggle/locales/zh_tw_cli.py +85 -0
- agent_toggle-0.5.2/agent_toggle/locales/zh_tw_common.py +7 -0
- agent_toggle-0.5.2/agent_toggle/locales/zh_tw_config.py +89 -0
- agent_toggle-0.5.2/agent_toggle/locales/zh_tw_picker.py +80 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/mechanisms.py +355 -70
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/ops.py +17 -4
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/output.py +13 -4
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/profiles.py +25 -13
- agent_toggle-0.5.2/agent_toggle/settings.py +184 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/shims/claude.md.tmpl +26 -4
- agent_toggle-0.5.2/agent_toggle/shims/generic.md.tmpl +68 -0
- agent_toggle-0.5.2/agent_toggle/spinner.py +54 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/store.py +41 -0
- agent_toggle-0.5.2/agent_toggle/toml_check.py +273 -0
- agent_toggle-0.5.2/agent_toggle/ui/config_menu.py +622 -0
- agent_toggle-0.5.2/agent_toggle/ui/menu.py +134 -0
- agent_toggle-0.5.2/agent_toggle/ui/model.py +245 -0
- agent_toggle-0.5.2/agent_toggle/ui/picker.py +503 -0
- agent_toggle-0.5.2/agent_toggle/ui/theme.py +365 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/undo.py +6 -1
- agent_toggle-0.5.2/agent_toggle/update_check.py +359 -0
- agent_toggle-0.5.2/agent_toggle/update_prompt.py +201 -0
- agent_toggle-0.5.2/agent_toggle.egg-info/PKG-INFO +884 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle.egg-info/SOURCES.txt +32 -1
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle.egg-info/requires.txt +3 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/pyproject.toml +2 -1
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_alias.py +70 -11
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_backends.py +81 -6
- agent_toggle-0.5.2/tests/test_cli.py +393 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_cli_surface.py +65 -1
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_color.py +2 -0
- agent_toggle-0.5.2/tests/test_completions.py +37 -0
- agent_toggle-0.5.2/tests/test_config.py +174 -0
- agent_toggle-0.5.2/tests/test_config_menu.py +420 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_conformance.py +11 -2
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_containment.py +2 -3
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_cost.py +109 -0
- agent_toggle-0.5.2/tests/test_crash.py +379 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_doctor.py +209 -8
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_fs.py +165 -9
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_harnesses.py +11 -4
- agent_toggle-0.5.2/tests/test_help.py +138 -0
- agent_toggle-0.5.2/tests/test_i18n.py +172 -0
- agent_toggle-0.5.2/tests/test_install.py +269 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_mechanisms.py +2 -3
- agent_toggle-0.5.2/tests/test_menu.py +243 -0
- agent_toggle-0.5.2/tests/test_picker.py +691 -0
- agent_toggle-0.5.2/tests/test_platform.py +107 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_profiles.py +16 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_project.py +193 -2
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_safety.py +4 -6
- agent_toggle-0.5.2/tests/test_secret_audit.py +276 -0
- agent_toggle-0.5.2/tests/test_settings.py +166 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_store.py +2 -2
- agent_toggle-0.5.2/tests/test_theme.py +318 -0
- agent_toggle-0.5.2/tests/test_update_check.py +421 -0
- agent_toggle-0.5.2/tests/test_update_prompt.py +41 -0
- agent_toggle-0.1.0/PKG-INFO +0 -580
- agent_toggle-0.1.0/README.md +0 -550
- agent_toggle-0.1.0/agent_toggle/backends/plugin_cli.py +0 -41
- agent_toggle-0.1.0/agent_toggle/doctor.py +0 -285
- agent_toggle-0.1.0/agent_toggle/ui/picker.py +0 -279
- agent_toggle-0.1.0/agent_toggle.egg-info/PKG-INFO +0 -580
- agent_toggle-0.1.0/tests/test_cli.py +0 -99
- agent_toggle-0.1.0/tests/test_install.py +0 -98
- agent_toggle-0.1.0/tests/test_picker.py +0 -195
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/LICENSE +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/__main__.py +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/backends/__init__.py +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle/ui/__init__.py +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle.egg-info/dependency_links.txt +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle.egg-info/entry_points.txt +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/agent_toggle.egg-info/top_level.txt +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/setup.cfg +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_companions.py +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_encoding.py +0 -0
- {agent_toggle-0.1.0 → agent_toggle-0.5.2}/tests/test_undo.py +0 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-toggle
|
|
3
|
+
Version: 0.5.2
|
|
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
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/weskao/agent-toggle
|
|
7
|
+
Project-URL: Repository, https://github.com/weskao/agent-toggle
|
|
8
|
+
Project-URL: Issues, https://github.com/weskao/agent-toggle/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/weskao/agent-toggle/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: ai-agents,claude-code,codex,skills,mcp,cli,toggle
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development
|
|
23
|
+
Classifier: Topic :: Utilities
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Provides-Extra: windows
|
|
28
|
+
Requires-Dist: windows-curses; sys_platform == "win32" and extra == "windows"
|
|
29
|
+
Provides-Extra: telegram
|
|
30
|
+
Requires-Dist: telegram-kit<0.3,>=0.2.2; extra == "telegram"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# agent-toggle
|
|
34
|
+
|
|
35
|
+
Temporarily disable and restore AI-agent resources — skills, agents, commands,
|
|
36
|
+
rules, plugins, MCP servers — across Claude Code, Codex, Grok CLI, OpenCode,
|
|
37
|
+
OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at
|
|
38
|
+
session start.
|
|
39
|
+
|
|
40
|
+
**Nothing is ever deleted.** Everything is parked and recorded, and `enable`
|
|
41
|
+
puts it back where it came from.
|
|
42
|
+
|
|
43
|
+
## Why
|
|
44
|
+
|
|
45
|
+
| target | native mechanism | the gap |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| plugin | `claude plugin disable/enable` | fine — this tool just batches it |
|
|
48
|
+
| skill | none | moving files by hand hits the rename trap below |
|
|
49
|
+
| agent | none | same |
|
|
50
|
+
| command | none | same, plus nested `group/name.md` paths get flattened |
|
|
51
|
+
| MCP | `remove` only | the config is gone unless you saved it first |
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
Latest release, from [PyPI](https://pypi.org/project/agent-toggle/):
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
uv tool install agent-toggle
|
|
59
|
+
agent-toggle --version
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Straight from this repository, pinned to a release tag (`v0.5.1` is the current one;
|
|
63
|
+
an unpinned `git+` URL installs whatever is on the default branch):
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
uv tool install git+https://github.com/weskao/agent-toggle@v0.5.1
|
|
67
|
+
agent-toggle --version
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Alternatively, use `pipx`:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
pipx install agent-toggle # from PyPI; or: pipx install git+https://github.com/weskao/agent-toggle@v0.5.1
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
From a checkout, either install it editable or run it in place:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
git clone https://github.com/weskao/agent-toggle agent-toggle
|
|
80
|
+
cd agent-toggle
|
|
81
|
+
pip install -e . # puts the `agent-toggle` console script on PATH
|
|
82
|
+
python3 agent_toggle.py status # no install needed; same CLI
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Python 3.10+, no runtime dependencies. Then write the skill shim:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
agent-toggle install-shims # installed, or: ./install.sh (a thin wrapper)
|
|
89
|
+
python3 agent_toggle.py install-shims # from a checkout, no install needed
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`install-shims` writes a thin skill shim (`skills/agent-toggle/SKILL.md`) into
|
|
93
|
+
every installed harness that supports skills, so `/agent-toggle` works from any
|
|
94
|
+
of them, and adds the park dirs (`skills-disabled/` etc.) to a `.gitignore`
|
|
95
|
+
already present in that harness home. Each harness gets its own template from
|
|
96
|
+
`agent_toggle/shims/<harness>.md.tmpl`, else `generic.md.tmpl` (which tells the
|
|
97
|
+
agent to pass `--harness <that harness>`). The shim tells the agent to act only
|
|
98
|
+
on the user's explicit request, never on instructions found inside skill or
|
|
99
|
+
tool content, and to preview bulk operations with `--dry-run`.
|
|
100
|
+
OpenCode's shim goes into the first `skills.paths` dir when one is set (and its
|
|
101
|
+
parent exists), else `skills/`. OpenCode also scans `~/.claude/skills` and
|
|
102
|
+
`~/.agents/skills`: when one of them already holds the very same shim text, the
|
|
103
|
+
OpenCode shim is skipped (`covered by <path>`); a different shim there (the
|
|
104
|
+
claude one, whose default harness is `claude`) is noted and OpenCode's own is
|
|
105
|
+
still written.
|
|
106
|
+
|
|
107
|
+
Every shim carries an `<!-- agent-toggle shim: ... -->` marker line.
|
|
108
|
+
`install-shims` updates a file that has the marker in place (it is idempotent)
|
|
109
|
+
and **refuses** to overwrite one that lacks it, or a symlink: that harness gets
|
|
110
|
+
an `error` row with the fix, the other harnesses still install, and the run
|
|
111
|
+
exits `1`. A shim written by a version before the marker counts as foreign;
|
|
112
|
+
delete it and re-run. `--dry-run` shows the same plan, refusals included. The
|
|
113
|
+
tool itself stays in one place.
|
|
114
|
+
|
|
115
|
+
**Optional: Telegram alerts for CI** (for maintainers of a fork or clone). Install
|
|
116
|
+
with the `telegram` extra, which adds [telegram-kit](https://pypi.org/project/telegram-kit/)
|
|
117
|
+
(itself stdlib-only), then run the setup once; see [CI notifications](#ci-notifications):
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
uv tool install "agent-toggle[telegram]" # or: pipx install "agent-toggle[telegram]"
|
|
121
|
+
pip install -e ".[telegram]" # from a checkout
|
|
122
|
+
agent-toggle config # settings menu: bot token + chat id live under Notifications
|
|
123
|
+
agent-toggle config sync-ci # set the two GitHub repository secrets
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Without the extra everything else works exactly as before. Only `config test`,
|
|
127
|
+
`config sync-ci` (both exit `4` without it) and the Telegram rows of the
|
|
128
|
+
[settings menu](#settings--config-menu) need it; bare `config` still opens, and
|
|
129
|
+
those rows read `install agent-toggle[telegram]`.
|
|
130
|
+
|
|
131
|
+
## Shell completion
|
|
132
|
+
|
|
133
|
+
Each release publishes static completion files for bash, zsh and fish as the
|
|
134
|
+
`completions` artifact of its Release workflow run. They are generated from the
|
|
135
|
+
CLI parser; to build them from a checkout:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
python3 tools/gen_completions.py completions # agent-toggle.bash, _agent-toggle (zsh), agent-toggle.fish
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Source `agent-toggle.bash` from `~/.bashrc`, put `_agent-toggle` on your zsh
|
|
142
|
+
`fpath`, or copy `agent-toggle.fish` to `~/.config/fish/completions/`.
|
|
143
|
+
|
|
144
|
+
## Usage
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [args]
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| command | what it does |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `ui` (alias `pick`) | interactive picker — harness tabs, type groups, cost bars, search, sort, profiles (`p`); `--dry-run` shows the plan for what you stage and changes nothing; `--project <dir>` picks in a repo's own scope; see [Interactive picker](#interactive-picker) |
|
|
153
|
+
| `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`) |
|
|
154
|
+
| `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
|
|
155
|
+
| `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) |
|
|
156
|
+
| `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); on a terminal, then asks y/n for each problem `doctor` can fix (see [Fixing](#fixing)) |
|
|
157
|
+
| `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
|
|
158
|
+
| `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
|
|
159
|
+
| `enable --all` | put back **every** disabled item (`--harness H` narrows it, `--project <dir>` takes only that project's) |
|
|
160
|
+
| `undo` | reverse the last logged batch (`--dry-run` shows the plan) |
|
|
161
|
+
| `profile save\|apply\|diff\|list` | named sets of live items; see [Profiles](#profiles) |
|
|
162
|
+
| `config` | settings menu (curses, else a numbered list); `config --json` lists every setting; see [Settings & config menu](#settings--config-menu) |
|
|
163
|
+
| `config test\|sync-ci` | send one Telegram test message / set the GitHub CI secrets; needs the `[telegram]` extra; see [CI notifications](#ci-notifications) |
|
|
164
|
+
| `help [command]` | styled help; `help ui` = `--help ui` = `ui --help`; see [Help](#help) |
|
|
165
|
+
| `doctor` | check each harness layout and `state.json` against disk; exit `1` only on an `error` row; on a terminal, asks y/n per fix it can run itself |
|
|
166
|
+
| `migrate` | import an older `~/.claude-toggle/` state |
|
|
167
|
+
|
|
168
|
+
Every command also works with a leading `--` (`agent-toggle --status`, `--help`, `--config`); this README uses the plain form.
|
|
169
|
+
|
|
170
|
+
`<type>` = `skill` / `agent` / `command` / `rule` / `plugin` / `mcp`.
|
|
171
|
+
`rule` is claude-only (`~/.claude/rules/*.md`, parked in `rules-disabled/`).
|
|
172
|
+
Disabling a rule prints a warning (also in `--json` `warnings`, dry run included)
|
|
173
|
+
that rules may carry safety constraints.
|
|
174
|
+
|
|
175
|
+
Flags accepted by every command, before or after the subcommand:
|
|
176
|
+
|
|
177
|
+
- `--harness claude|codex|grok|opencode|openclaw|copilot|vibe|devin|agy` picks the
|
|
178
|
+
target (default `claude`; on `list` / `status` / `cost` it filters when given).
|
|
179
|
+
- `--json` prints exactly one JSON document and nothing else on stdout:
|
|
180
|
+
`{"ok", "command", "results": [{harness, type, name, action, status, detail, ...}],
|
|
181
|
+
"warnings", "needs_new_session"}`. Errors -- including unexpected ones, as
|
|
182
|
+
`{ExceptionType}: {message}` -- always emit it, with `"ok": false`.
|
|
183
|
+
Exception: `--help` / `--version` print plain text even with `--json`, and
|
|
184
|
+
`ui` is interactive so it rejects `--json` (exit 2).
|
|
185
|
+
- `--project <dir>` (`disable` / `enable` / `enable --all` / `list` / `ui` / `cost` / `profile
|
|
186
|
+
save|apply|diff`) switches to project scope; see [Project scope](#project-scope).
|
|
187
|
+
- `--version` prints the version.
|
|
188
|
+
- `AGENT_TOGGLE_CLI_TIMEOUT=<seconds>` overrides the timeout of every `claude` CLI call
|
|
189
|
+
(default 30 s for read-only `plugin list`, 120 s for every other call); a
|
|
190
|
+
non-positive or non-numeric value is ignored with a warning.
|
|
191
|
+
- `-v` / `--verbose` (or `AGENT_TOGGLE_DEBUG=1`) adds a traceback on stderr for
|
|
192
|
+
unexpected errors; otherwise they are a single `error:` line.
|
|
193
|
+
- `--color auto|always|never` sets ANSI color for human output: green ok, red errors,
|
|
194
|
+
yellow warnings, cyan harness names, dim secondary text (`status`, `list`, `cost`,
|
|
195
|
+
`disable` / `enable`, `doctor`, warnings and errors; the picker uses curses color pairs
|
|
196
|
+
and stays monochrome when the terminal has no colors). `--json` is never colored.
|
|
197
|
+
`never` turns it off and `always` forces it even when piped; `auto` (default) checks
|
|
198
|
+
`NO_COLOR`, then `FORCE_COLOR`, then `TERM=dumb`, and otherwise colors only on a TTY.
|
|
199
|
+
Precedence: `--color` flag > `AGENT_TOGGLE_COLOR` > the `color` [setting](#settings--config-menu)
|
|
200
|
+
> `auto`; `NO_COLOR` still wins over `auto`.
|
|
201
|
+
`always` still stays plain on a Windows console that cannot do ANSI.
|
|
202
|
+
|
|
203
|
+
Extra row fields: `list` rows carry `at`, `mechanism`, `companions`; `cost` rows
|
|
204
|
+
carry `enabled`, `tokens`, `would_save`, `chars`, and the final `total` row
|
|
205
|
+
carries `total_tokens`, `saved_tokens`, `formula`. Rows for a
|
|
206
|
+
directory shared with another harness (see below) carry `shared_with`, the other
|
|
207
|
+
harnesses that read it: `disable` / `enable` rows relative to the harness you
|
|
208
|
+
asked, plus `owner`, the harness whose park dir and state entry hold the item;
|
|
209
|
+
`list` and `cost` rows are filed under the owner (`shared_with` is `[]` when
|
|
210
|
+
unshared).
|
|
211
|
+
|
|
212
|
+
`--dry-run` (`disable` / `enable` / `enable --all` / `undo` / `profile apply` /
|
|
213
|
+
`install-shims` / `ui`) computes the plan --
|
|
214
|
+
moves, companions, backups, MCP edits, warnings -- and writes nothing: no state,
|
|
215
|
+
log, lock or backup, and no chmod; it never shells out to `claude`. `disable` /
|
|
216
|
+
`enable` result rows have action `would-disable` / `would-enable`; every plan
|
|
217
|
+
row has status `planned`. Read-only commands (`list`, `status`, `cost`) also
|
|
218
|
+
change nothing on disk -- `status` warns about a `state.json` or
|
|
219
|
+
backup looser than `0600` instead of fixing it. A dry run whose plan contains a
|
|
220
|
+
failing item exits `1`, like the real run would.
|
|
221
|
+
|
|
222
|
+
| exit code | meaning |
|
|
223
|
+
|:-:|---|
|
|
224
|
+
| `0` | ok |
|
|
225
|
+
| `1` | partial failure (some items failed), or an unexpected error |
|
|
226
|
+
| `2` | usage error, including an unknown harness or type |
|
|
227
|
+
| `3` | locked by another run |
|
|
228
|
+
| `4` | unsupported harness/type pair, or harness not installed |
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
agent-toggle disable skill demo-skill other-skill
|
|
232
|
+
agent-toggle disable command demo:batch # nested commands/demo/batch.md
|
|
233
|
+
agent-toggle disable agent demo-agent
|
|
234
|
+
agent-toggle disable mcp example-mcp
|
|
235
|
+
agent-toggle disable skill demo-skill --harness codex
|
|
236
|
+
agent-toggle enable mcp example-mcp
|
|
237
|
+
agent-toggle cost --type skill --json
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
A colon addresses nesting: `demo:batch` is `commands/demo/batch.md`.
|
|
241
|
+
|
|
242
|
+
## Interactive picker
|
|
243
|
+
|
|
244
|
+
```sh
|
|
245
|
+
agent-toggle ui # alias: agent-toggle pick
|
|
246
|
+
agent-toggle # same, on a terminal; elsewhere prints help and exits 2
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
agent-toggle v0.5.1 5 resources · ~6.3k tok live · 1 staged (+300 tok)
|
|
251
|
+
All 5 │ claude 4 │ codex 2
|
|
252
|
+
🔍 Type to search (name, path, fuzzy) type: All sort: name
|
|
253
|
+
Skills 3 ─────────────────────────────────────────────────────────────│ beta
|
|
254
|
+
● alpha claude 1.2k █▎ │
|
|
255
|
+
› ● + beta claude (300) ▎ │ harness claude
|
|
256
|
+
● zeta claude 5.0k █████ │ type skill
|
|
257
|
+
Agents 1 ─────────────────────────────────────────────────────────────│ state ○ parked
|
|
258
|
+
● gamma claude 50 ▏ +shared │ staged → live
|
|
259
|
+
Commands 1 ───────────────────────────────────────────────────────────│ path ~/.claude/skills-disabled/beta
|
|
260
|
+
● delta codex 10 ▏ │ cost 0 now; ~300 tok if restored
|
|
261
|
+
│ since 2026-09-19 10:00:00
|
|
262
|
+
5 of 5 shown · 2/5
|
|
263
|
+
Space toggle Enter apply Esc cancel ? help / search ←→ harness t type s sort p profile a all
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
What is on screen:
|
|
267
|
+
|
|
268
|
+
- **Title bar**: version and a summary (resources, live tokens, staged changes and their token effect).
|
|
269
|
+
- **Harness tab bar**: `All` plus each enabled harness, with counts. Harnesses switched off in
|
|
270
|
+
the [settings](#settings--config-menu) are hidden.
|
|
271
|
+
- **Rows grouped by type** (Skills, Agents, Commands, ...) under a heading with a count.
|
|
272
|
+
`●` is live, `○` parked (`*` / `o` in ASCII); a `+` / `-` before the name marks a staged change,
|
|
273
|
+
and `+shared` marks a directory another harness also reads.
|
|
274
|
+
- **Cost column**: estimated startup tokens (chars / 4, about +-25 %) with a colored bar. A parked
|
|
275
|
+
row shows `(N)` in brackets, what restoring it would load. Plugins appear as rows too (via
|
|
276
|
+
`claude plugin list --json`; skipped under `ui --dry-run`, which never shells out).
|
|
277
|
+
- **Detail pane** at 100 columns or wider: harness, type, state, staged, path, cost, since,
|
|
278
|
+
mechanism, and what it is shared with.
|
|
279
|
+
- **Search bar** (🔍; plain `/` on terminals that cannot draw emoji: the Linux console, the legacy
|
|
280
|
+
Windows console, non-UTF-8 locales) under the tabs, always visible.
|
|
281
|
+
- **Per-harness colors** on the harness column, matching `ai-accounts list` (claude orange on
|
|
282
|
+
256-color terminals, codex cyan, agy blue, grok yellow, vibe green, copilot red, opencode magenta).
|
|
283
|
+
- **Key-chip footer** and a `?` help overlay. A spinner shows on stderr while the list loads
|
|
284
|
+
(only on a TTY; off with `NO_COLOR`). The mouse wheel scrolls (xterm alternate-scroll mode).
|
|
285
|
+
|
|
286
|
+
| key | action |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `Space` | toggle the highlighted row (live / parked) and stay on it (while filtering, `Space` types a space) |
|
|
289
|
+
| `Tab` | toggle the highlighted row and advance to the next |
|
|
290
|
+
| `Enter` | apply every staged change (with `--dry-run`: show the plan) |
|
|
291
|
+
| `Esc` / `Ctrl-C` | cancel; nothing is applied |
|
|
292
|
+
| `↑` `↓` `PgUp` `PgDn` `Home` `End`, `Ctrl-P` / `Ctrl-N` | move |
|
|
293
|
+
| `/` | start a search; any other non-command letter starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
|
|
294
|
+
| `Backspace` / `Ctrl-U` | delete one character / clear the filter |
|
|
295
|
+
| `←` `→` | switch harness tab (also while typing a filter); `0` = All, `1`-`9` = that tab, `h` = next tab |
|
|
296
|
+
| `t` | cycle the type filter |
|
|
297
|
+
| `s` | cycle sort: name, cost (biggest first); applies within each type group |
|
|
298
|
+
| `p` | profiles: stage a saved one, or save the live state (see below) |
|
|
299
|
+
| `?` | show the key list |
|
|
300
|
+
| `a` / `Ctrl-A` | toggle every visible row |
|
|
301
|
+
|
|
302
|
+
The command keys (`t`, `s`, `p`, `h`, `a`, `0`-`9`, `?`) act only while no filter is active.
|
|
303
|
+
Press `/` first to type a filter that begins with one of them; once the filter is non-empty,
|
|
304
|
+
every letter just types.
|
|
305
|
+
|
|
306
|
+
The start view (sort, harness tab, type filter) comes from the `picker_sort`, `picker_harness`
|
|
307
|
+
and `picker_type` [settings](#settings--config-menu). A profile skips harnesses that are hidden
|
|
308
|
+
and counts those items as `hidden by settings`; `ui --harness <name>` shows that harness even
|
|
309
|
+
when it is switched off.
|
|
310
|
+
|
|
311
|
+
`p` opens a prompt over the list of saved profiles: type a number or name (or
|
|
312
|
+
`apply <name>`) and Enter to **stage** that profile's changes (only the items it
|
|
313
|
+
mentions; ones this machine lacks are skipped), then Enter in the picker applies
|
|
314
|
+
them like any other change, so `ui --dry-run` previews a profile and a stray
|
|
315
|
+
`p` changes nothing. `save <name>` writes the live state (not your staged changes) as
|
|
316
|
+
a profile, with the same name rules and project scope as `profile save`; it is
|
|
317
|
+
off under `--dry-run`. Esc closes the prompt.
|
|
318
|
+
|
|
319
|
+
Nothing happens while the picker is open. Changes are staged, the screen is
|
|
320
|
+
torn down, and only then do the real operations run, so their output (which
|
|
321
|
+
companion files moved, which were kept because they are shared) is readable
|
|
322
|
+
instead of fighting curses for the terminal.
|
|
323
|
+
|
|
324
|
+
Built on stdlib `curses`, so there is nothing to install on macOS and Linux
|
|
325
|
+
(on Windows, `pip install "agent-toggle[windows]"` pulls `windows-curses`).
|
|
326
|
+
Without curses, `ui` falls back to a numbered menu with the same staging and the
|
|
327
|
+
same result. It is grouped by type, with the same glyphs and color: type row
|
|
328
|
+
numbers (`1 3 5-7`) to tick or untick, `/text` to filter (`/` alone clears it),
|
|
329
|
+
`s` / `h` / `t` to sort and cycle the harness and type filters, `p` to list profiles,
|
|
330
|
+
`p <number|name>` to stage one and `p save <name>` to save the live state, `a` to
|
|
331
|
+
apply, `q` (or end of input) to cancel.
|
|
332
|
+
|
|
333
|
+
The chrome (title, tabs, footer, help, detail labels) is translated when `language` is
|
|
334
|
+
`zh-TW`; item names and paths are never translated.
|
|
335
|
+
|
|
336
|
+
## Settings & config menu
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
agent-toggle config # or: agent-toggle --config
|
|
340
|
+
agent-toggle config --json # every setting with its value and source; the token is masked
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
`config` opens the settings menu: curses on a terminal, a numbered list otherwise. Each change
|
|
344
|
+
is saved the moment you make it. On a numbered list, end of input, `q` or an empty line exits `0`
|
|
345
|
+
(safe in CI; note it reads piped digits, so an open stdin pipe waits).
|
|
346
|
+
|
|
347
|
+
| group | rows |
|
|
348
|
+
|---|---|
|
|
349
|
+
| General | Check for updates, Color, Language, Default harness |
|
|
350
|
+
| Harnesses | one On/Off per harness, with `found ~/.x` or `not on this machine` |
|
|
351
|
+
| Picker | default sort, harness filter, type filter |
|
|
352
|
+
| Notifications | Telegram bot token (masked), Telegram chat ID |
|
|
353
|
+
| 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 |
|
|
354
|
+
|
|
355
|
+
Keys: `↑` `↓` move (wraps, skips headings), `←` `→` cycle a value, `Enter` / `Space` change or run
|
|
356
|
+
the row, `r` reset the row, `R` reset everything (asks y/n), `q` / `Esc` / `Ctrl-C` quit. In an
|
|
357
|
+
inline text field `Enter` commits, `Esc` cancels and `-` then `Enter` clears. A dim `env` tag
|
|
358
|
+
marks a row whose value is overridden by an environment variable. The Language row switches the
|
|
359
|
+
UI live.
|
|
360
|
+
|
|
361
|
+
Settings live in `~/.agent-toggle/config.json` (mode `0600`, written atomically; keys the
|
|
362
|
+
tool does not know are kept):
|
|
363
|
+
|
|
364
|
+
| key | values | default | env override |
|
|
365
|
+
|---|---|---|---|
|
|
366
|
+
| `update_check` | `true` / `false` | `true` | `AGENT_TOGGLE_UPDATE_CHECK` |
|
|
367
|
+
| `color` | `auto` / `always` / `never` | `auto` | `AGENT_TOGGLE_COLOR` |
|
|
368
|
+
| `language` | `en` / `zh-TW` | `en` | `AGENT_TOGGLE_LANG` |
|
|
369
|
+
| `default_harness` | a harness name | `claude` | `AGENT_TOGGLE_DEFAULT_HARNESS` |
|
|
370
|
+
| `harness.<name>` | `true` / `false`, for `claude` `codex` `grok` `opencode` `openclaw` `copilot` `vibe` `devin` `agy` | `true` | |
|
|
371
|
+
| `picker_sort` | `name` / `cost` | `name` | |
|
|
372
|
+
| `picker_harness` | `all` or a harness name | `all` | |
|
|
373
|
+
| `picker_type` | `all` or a type | `all` | |
|
|
374
|
+
| `telegram_chat_id` | a number, `-100...` or `@channel` | unset | `TG_CHAT_ID` |
|
|
375
|
+
|
|
376
|
+
The bot token is not in this file: it is in the OS keystore through telegram-kit
|
|
377
|
+
(`TG_BOT_TOKEN` overrides it). An environment variable always beats the file; an
|
|
378
|
+
invalid value in either is ignored with one warning on stderr.
|
|
379
|
+
|
|
380
|
+
- `harness.<name>` off hides that harness from `ui`, `list`, `status` and `cost` (`list`, `status` and
|
|
381
|
+
`cost` print a dim `N hidden by settings` note, and `--json` carries it in `warnings`; an item shared with a harness that is still
|
|
382
|
+
on stays listed) and `install-shims` skips it; an explicit
|
|
383
|
+
`--harness <name>` still reaches it, `ui --harness <name>` included.
|
|
384
|
+
- `default_harness` is what `disable` / `enable` act on without `--harness`, even when that
|
|
385
|
+
harness is switched off (with one warning on stderr);
|
|
386
|
+
`disable` / `enable --project` without `--harness` still default to `claude`.
|
|
387
|
+
- `language` translates the config menu, help, update prompt and picker chrome; command output stays English.
|
|
388
|
+
- Bare `config` works without the `[telegram]` extra; `config test` and `config sync-ci` are
|
|
389
|
+
unchanged and need it (exit `4` without it).
|
|
390
|
+
|
|
391
|
+
## Update check
|
|
392
|
+
|
|
393
|
+
On every invocation (unless disabled) agent-toggle checks PyPI for a newer release. It starts
|
|
394
|
+
before the command and is offered after it, on every exit path; it overlaps the command and waits at most 0.8 s afterwards.
|
|
395
|
+
|
|
396
|
+
- **On a terminal** (stdin and stderr are TTYs, no `--json`): a panel on stderr, "agent-toggle X
|
|
397
|
+
is available (you have Y)", with `Update now`, `Skip` and `Skip until next version`, a release
|
|
398
|
+
notes link, and `↑` `↓` `Enter` `q`. `Update now` runs `uv tool upgrade agent-toggle`; if that
|
|
399
|
+
fails you get a yellow warning with the command to run yourself.
|
|
400
|
+
- **Off a terminal**: two stderr lines, `agent-toggle X is available (you have Y)` and
|
|
401
|
+
` uv tool upgrade agent-toggle`.
|
|
402
|
+
- It never changes the exit code or stdout, so `--json` output stays one document.
|
|
403
|
+
|
|
404
|
+
It is one `GET https://pypi.org/pypi/agent-toggle/json` on a background thread with a 0.8 s
|
|
405
|
+
timeout, cached for 10 minutes in `~/.agent-toggle/update-check.json`. Nothing is sent beyond a
|
|
406
|
+
normal HTTP request with the User-Agent `agent-toggle-update-check`. See `SECURITY.md`
|
|
407
|
+
for the hardening. To turn it off, set `AGENT_TOGGLE_UPDATE_CHECK=0` or the `update_check` setting
|
|
408
|
+
to off (the settings menu's "Check for updates").
|
|
409
|
+
|
|
410
|
+
## Help
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
agent-toggle help # grouped overview
|
|
414
|
+
agent-toggle help config # one command; same as: --help config, config --help
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Help is grouped as Toggle (`ui`, `disable`, `enable`, `undo`, `profile`), Inspect (`status`,
|
|
418
|
+
`list`, `cost`, `doctor`) and Setup (`config`, `install-shims`, `migrate`), with the global
|
|
419
|
+
options, examples and exit codes. `help <command>` shows that command's usage, arguments,
|
|
420
|
+
options and examples; `help help` is the overview, `help version` shows how to print the version,
|
|
421
|
+
and an unknown command exits `2`. It honours `--color` and `NO_COLOR`, and is
|
|
422
|
+
translated when `language` is `zh-TW`.
|
|
423
|
+
|
|
424
|
+
## What each harness supports
|
|
425
|
+
|
|
426
|
+
| harness | home | skill | agent | command | rule | plugin | mcp |
|
|
427
|
+
|---|---|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
428
|
+
| claude | `~/.claude` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ `~/.claude.json` |
|
|
429
|
+
| codex | `~/.codex` | ✓ | ✓ | ✓ (`commands/` + `prompts/`) | — | — | ✓ `config.toml` |
|
|
430
|
+
| grok | `~/.grok` | ✓ | — | — | — | — | ✓ `config.toml` |
|
|
431
|
+
| opencode | `$XDG_CONFIG_HOME/opencode`, else `~/.config/opencode` | ✓ | — | ✓ (`command/`) | — | — | ✓ `opencode.json` (flag, *assumed*) |
|
|
432
|
+
| openclaw | `~/.openclaw` | ✓ (dir move, or flag when `skills.entries.<name>` exists, *assumed*) | ✓ | — | — | ✓ `openclaw.json` (flag, *assumed*) | — (sqlite) |
|
|
433
|
+
| copilot | `~/.copilot` | ✓ | ✓ | — | — | — | ✓ `mcp-config.json` |
|
|
434
|
+
| vibe | `~/.vibe` | ✓ | — | — | — | — | — |
|
|
435
|
+
| devin | `~/.devin` | — | — | — | — | — | — |
|
|
436
|
+
| agy | `~/.antigravity` | — | — | — | — | — | — |
|
|
437
|
+
|
|
438
|
+
Per-harness locations, mechanisms and the harness versions this was checked
|
|
439
|
+
against are in [docs/harnesses.md](docs/harnesses.md).
|
|
440
|
+
|
|
441
|
+
**Homes per OS.** Every `~` above is Python's `Path.home()`: `$HOME` on macOS and
|
|
442
|
+
Linux, `%USERPROFILE%` on Windows (so `~/.claude` is `C:\Users\<you>\.claude`).
|
|
443
|
+
opencode honours an absolute `$XDG_CONFIG_HOME` on every OS and otherwise uses
|
|
444
|
+
`~/.config/opencode`, Windows included; the tool does not look in `%APPDATA%`. This
|
|
445
|
+
tool's own state lives in `~/.agent-toggle/`. The Linux and Windows locations
|
|
446
|
+
follow the harnesses' documentation and have not yet been checked against real
|
|
447
|
+
installs there; if a harness keeps its files elsewhere, `status` shows it as not
|
|
448
|
+
installed.
|
|
449
|
+
|
|
450
|
+
Plugin ids are passed to `claude plugin ...`, which on Windows runs through
|
|
451
|
+
`cmd.exe`. An id with anything outside `A-Z a-z 0-9 . _ @ : / -` (for example `&`,
|
|
452
|
+
`%`, `|` or a space) is refused on every OS before any command runs.
|
|
453
|
+
|
|
454
|
+
Copilot's `config.json` is machine-managed and never edited by this tool; Copilot
|
|
455
|
+
plugins are not yet supported. `claude` plugins are toggled through `claude
|
|
456
|
+
plugin enable/disable`; `openclaw` plugins through a config flag (see [Assumed
|
|
457
|
+
formats](#assumed-formats)). Codex has no plugin CLI this tool can drive: `disable
|
|
458
|
+
plugin x --harness codex` exits `4`, which is why the matrix leaves it unchecked.
|
|
459
|
+
|
|
460
|
+
Flag items (openclaw plugins and flagged skills, opencode mcp) are toggled with
|
|
461
|
+
`disable` / `enable`, `undo` and `enable --all`. The picker, `cost` and `profile`
|
|
462
|
+
list them live (read from the config file; only entries that carry a boolean
|
|
463
|
+
`enabled`, since a key is never invented) and parked (from `state.json`); an
|
|
464
|
+
openclaw skill whose flag was switched off by this tool is shown as disabled even
|
|
465
|
+
though its directory is still in place.
|
|
466
|
+
|
|
467
|
+
The grok MCP location (`~/.grok/config.toml`, the same `[mcp_servers.<name>]`
|
|
468
|
+
tables as codex, including a `.headers` sub-table for remote servers) was
|
|
469
|
+
confirmed on a live install. Backups keep every sub-table verbatim.
|
|
470
|
+
|
|
471
|
+
Unsupported pairs fail loudly. OpenClaw keeps MCP servers in
|
|
472
|
+
`state/openclaw.sqlite`, not a file this tool can safely slice, so it refuses
|
|
473
|
+
rather than guessing.
|
|
474
|
+
|
|
475
|
+
### Shared directories
|
|
476
|
+
|
|
477
|
+
OpenCode may read skills from another harness's directory through
|
|
478
|
+
`opencode.json` → `skills.paths` (absolute, `~`, `$HOME/...`
|
|
479
|
+
and `${HOME}/...` entries; read from `opencode.json`, else `opencode.jsonc`;
|
|
480
|
+
relative entries are skipped, since OpenCode resolves them against the session
|
|
481
|
+
directory). OpenCode also always scans `~/.claude/skills` and `~/.agents/skills`,
|
|
482
|
+
so claude's skills count as shared with it. Such a directory is **one** item, filed under
|
|
483
|
+
its owner (the harness whose home really holds it): it is
|
|
484
|
+
parked once, tracked once, and every row reports `shared_with`, the other
|
|
485
|
+
harnesses it also affects. `status` prints `shared dir with: ...`.
|
|
486
|
+
|
|
487
|
+
If the live directory carries `.synced-from-*` markers, a sync job may
|
|
488
|
+
re-create what you parked; `status` and `disable` warn about it. Park in the
|
|
489
|
+
source harness instead. The warning is printed once per real directory, naming
|
|
490
|
+
every harness that views it.
|
|
491
|
+
|
|
492
|
+
## Companion files
|
|
493
|
+
|
|
494
|
+
A skill or command often calls a helper next to it — `scripts/foo.py`,
|
|
495
|
+
`tg-send.sh`. On `disable`, the item's text is scanned for such references and
|
|
496
|
+
each one is classified:
|
|
497
|
+
|
|
498
|
+
- **exclusive** (nothing else in the harness mentions it) → parked alongside
|
|
499
|
+
the item, restored with it.
|
|
500
|
+
- **shared** (anything else references it) → **left alone**, and reported.
|
|
501
|
+
|
|
502
|
+
Sharing is a veto, not a warning. `tg-send.sh` is referenced by five different
|
|
503
|
+
commands; moving it because one of them got disabled would silently break the
|
|
504
|
+
other four.
|
|
505
|
+
|
|
506
|
+
Detection reads file text, so a path built at runtime cannot be found. Every
|
|
507
|
+
companion decision is printed before anything moves, so a wrong guess is
|
|
508
|
+
visible rather than silent.
|
|
509
|
+
|
|
510
|
+
## MCP backups are verbatim
|
|
511
|
+
|
|
512
|
+
- **Claude**: the raw entry from `~/.claude.json`. Never `claude mcp get` —
|
|
513
|
+
that prints a human summary and silently drops auth fields (`headers`,
|
|
514
|
+
`headersHelper`), so a server restored from it fails with 401.
|
|
515
|
+
- **Codex**: the exact `[mcp_servers.<name>]` text block including its
|
|
516
|
+
`.tools.*` sub-tables. Python's stdlib reads TOML but cannot write it, and a
|
|
517
|
+
hand-rolled serializer would mangle comments — a text slice is lossless and
|
|
518
|
+
much less code.
|
|
519
|
+
|
|
520
|
+
MCP changes need a **new session** to take effect.
|
|
521
|
+
|
|
522
|
+
### Claude MCP scopes
|
|
523
|
+
|
|
524
|
+
Both scopes stored in `~/.claude.json` are togglable, and the scope round-trips:
|
|
525
|
+
|
|
526
|
+
| scope | lives in | toggle |
|
|
527
|
+
|---|---|---|
|
|
528
|
+
| user | top-level `mcpServers` | ✓ |
|
|
529
|
+
| local | `projects/<dir>/mcpServers` | ✓ — project path saved with the backup |
|
|
530
|
+
| project | the repo's own `.mcp.json` | ✓ with `--project <dir>` only, see [Project scope](#project-scope) |
|
|
531
|
+
| claude.ai connector | your account | ✓ — recorded in each *existing* project's `disabledMcpServers`; a project first opened later needs the toggle re-run |
|
|
532
|
+
|
|
533
|
+
`claude mcp remove -s local` only sees the project it runs in, so the project
|
|
534
|
+
path is recorded at disable time and the restore runs back in that directory.
|
|
535
|
+
One name that is local-scope in several projects is refused unless your cwd
|
|
536
|
+
picks the winner — guessing would restore it into the wrong project.
|
|
537
|
+
|
|
538
|
+
## Profiles
|
|
539
|
+
|
|
540
|
+
A profile is a named snapshot of which items are live, for the dotfiles repo
|
|
541
|
+
or a second machine. It holds only `{harness, type, name, live}` per item --
|
|
542
|
+
never a path or a secret.
|
|
543
|
+
|
|
544
|
+
```sh
|
|
545
|
+
agent-toggle profile save work # -> ~/.agent-toggle/profiles/work.json
|
|
546
|
+
agent-toggle profile save work --out ~/dotfiles/agent-toggle/work.json
|
|
547
|
+
agent-toggle profile diff work # what apply would do; writes nothing
|
|
548
|
+
agent-toggle profile apply ~/dotfiles/agent-toggle/work.json --dry-run
|
|
549
|
+
agent-toggle profile apply work
|
|
550
|
+
agent-toggle profile list
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**`apply` toggles only the items the profile mentions.** An item the profile
|
|
554
|
+
lists as live but that is parked now is enabled; one it lists as parked but
|
|
555
|
+
that is live now is disabled; everything else is left alone -- in particular an
|
|
556
|
+
item installed after the save is never touched. An item the profile names that
|
|
557
|
+
this machine does not have gets a `skipped` row (`not on this machine`), not a
|
|
558
|
+
failure. `apply` runs through the same path as `enable` / `disable` (one lock,
|
|
559
|
+
one batch, logged, undoable with `undo`).
|
|
560
|
+
|
|
561
|
+
- An argument ending in `.json` or containing a path separator is a **file
|
|
562
|
+
path** (absolute paths are fine); anything else is a stored profile name under
|
|
563
|
+
`~/.agent-toggle/profiles`. `..` in a path, and names that are not plain file
|
|
564
|
+
names, are refused (exit `2`), as are a bad version, an unknown harness or
|
|
565
|
+
type, an invalid item name, a duplicate item or a file over 1 MiB.
|
|
566
|
+
- `--out` belongs to `save` only; `--dry-run` to `apply` / `diff`. `--harness H`
|
|
567
|
+
narrows `save`, `apply` and `diff` to one harness.
|
|
568
|
+
- `diff` and `apply --dry-run` do not read plugin state (that needs the claude
|
|
569
|
+
CLI, and a dry run never shells out), so profile plugin items show as
|
|
570
|
+
`skipped` there.
|
|
571
|
+
- A user-scope profile never contains project-scope entries; with
|
|
572
|
+
`--project <dir>` the same commands save, diff and apply that project's
|
|
573
|
+
items (claude layout) instead -- the profile holds no directory, so `apply`
|
|
574
|
+
needs `--project` again. A project profile records `"scope": "project"`;
|
|
575
|
+
applying it without `--project` (or a user profile with `--project`) exits `2`,
|
|
576
|
+
so a project profile can never disable the user's items.
|
|
577
|
+
- `save` skips (with a warning) any item whose name `apply` would refuse, such as
|
|
578
|
+
an MCP server called `team/search`, so a saved profile always applies.
|
|
579
|
+
|
|
580
|
+
## Undo and `enable --all`
|
|
581
|
+
|
|
582
|
+
`undo` reads `~/.agent-toggle/log.jsonl`, takes the batch of the last
|
|
583
|
+
successful `disable` / `enable`, and reverses its rows in reverse order. The
|
|
584
|
+
reversal is logged as its own batch, so `undo` twice puts things back.
|
|
585
|
+
Project rows are replayed in their own project scope, never in user scope.
|
|
586
|
+
|
|
587
|
+
```sh
|
|
588
|
+
agent-toggle undo --dry-run
|
|
589
|
+
agent-toggle undo
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
- Nothing logged yet: `nothing to undo` (exit `0`). A log written before
|
|
593
|
+
batch ids existed: `nothing to undo: log predates undo` (exit `1`).
|
|
594
|
+
- `undo` rejects `--harness` (exit `2`): it reverses a whole batch.
|
|
595
|
+
- The log is a plain file you can edit; its names and project dirs are checked
|
|
596
|
+
like command-line input, and a project dir is trusted as written, exactly as
|
|
597
|
+
user-scope replay trusts the log.
|
|
598
|
+
|
|
599
|
+
`enable --all [--harness H] [--project <dir>]` restores every disabled entry in
|
|
600
|
+
scope, each in its own scope (`--project` takes only that project's).
|
|
601
|
+
`enable --all <name>` and `disable --all` are usage errors (exit `2`). Preview
|
|
602
|
+
bulk operations with `--dry-run`.
|
|
603
|
+
|
|
604
|
+
## Project scope
|
|
605
|
+
|
|
606
|
+
`--project <dir>` (`.` = the current directory) points `disable`, `enable`,
|
|
607
|
+
`enable --all`, `list` and `profile` at one repo: its `.claude/` dir types
|
|
608
|
+
(`skill`, `agent`, `command`, `rule`) and its own `.mcp.json` servers (`mcp`).
|
|
609
|
+
Claude layout only: `--harness codex --project ...` exits `4`, as does a
|
|
610
|
+
missing directory or one with neither `.claude/` nor `.mcp.json`. `$HOME`, its
|
|
611
|
+
ancestors, the tool's own state dir and the harness homes are refused (exit
|
|
612
|
+
`2`) -- that is user scope. `list`, `enable --all`, `cost` and `ui` run the same
|
|
613
|
+
checks. `cost --project` prices that project's items only (no plugins), and
|
|
614
|
+
`ui --project` stages and applies in project scope (its `p` key saves and applies
|
|
615
|
+
project profiles).
|
|
616
|
+
|
|
617
|
+
```sh
|
|
618
|
+
agent-toggle disable skill demo-skill --project .
|
|
619
|
+
agent-toggle disable mcp example-mcp --project ~/work/repo
|
|
620
|
+
agent-toggle list --project .
|
|
621
|
+
agent-toggle enable --all --project .
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
- Parked items go to `~/.agent-toggle/parked/<sha8>/`, **never inside the
|
|
625
|
+
project**; project and user state entries are separate, so one can never
|
|
626
|
+
restore into the other. Companion files are not moved in project scope.
|
|
627
|
+
- **A tracked file still disappears from the worktree.** Every project-scope
|
|
628
|
+
disable prints a warning like this one, and `git status` shows the deletion:
|
|
629
|
+
|
|
630
|
+
```
|
|
631
|
+
<repo>/.claude/skills/demo-skill is a tracked deletion in git status; restore with: agent-toggle enable skill demo-skill --project <repo>
|
|
632
|
+
<repo>/.mcp.json is a tracked change in git status; restore with: agent-toggle enable mcp example-mcp --project <repo>
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
Restore with that command, or `git checkout`; do not commit the deletion if
|
|
636
|
+
the repo is shared.
|
|
637
|
+
- `.mcp.json` is edited directly (no `claude` CLI) and must be **strict JSON**:
|
|
638
|
+
a BOM, comments or trailing commas are refused. `disable` cuts only that
|
|
639
|
+
server's entry, leaving every other byte as it was, and saves a backup at
|
|
640
|
+
`mcp-backups/<sha8>__claude__<name>.json` (mode `0600`) holding only that entry
|
|
641
|
+
-- its own headers, never another server's. `enable` puts the entry's bytes
|
|
642
|
+
back into the current file (an unchanged file comes back byte for byte; after
|
|
643
|
+
other edits it goes after its old neighbour, else at the end, indented like the
|
|
644
|
+
file) and checks that nothing else changed.
|
|
645
|
+
- A move across filesystems copies into a temp dir beside the target, renames it
|
|
646
|
+
into place, then deletes the source, so a killed run leaves either the source
|
|
647
|
+
intact or the target complete, and the next run settles it (see "Where state
|
|
648
|
+
lives"). `enable` removes the emptied `parked/<sha8>/*-disabled` and
|
|
649
|
+
`parked/<sha8>` dirs; a dir that still holds anything is kept.
|
|
650
|
+
- `status` prints one `project <dir>` line per project holding parked items.
|
|
651
|
+
A project with only `.mcp.json` has its live servers listed too in
|
|
652
|
+
`profile save --project`, next to its parked ones.
|
|
653
|
+
|
|
654
|
+
## Doctor
|
|
655
|
+
|
|
656
|
+
```sh
|
|
657
|
+
agent-toggle doctor [--harness H] [--json]
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
The check is read-only: no lock, no state write-back, no `claude` CLI call. For each
|
|
661
|
+
installed harness it compares the live layout with the table row (expected
|
|
662
|
+
dirs and config keys), then cross-checks `state.json` against disk (parked
|
|
663
|
+
item present, origin dir present, backup present, project dir present, entry
|
|
664
|
+
passes the same tamper checks `enable` runs, companion files present and not also
|
|
665
|
+
live, modes no looser than `0600` / `0700`). Rows (`action: doctor`) carry a status:
|
|
666
|
+
|
|
667
|
+
| status | meaning |
|
|
668
|
+
|---|---|
|
|
669
|
+
| `ok` | matches |
|
|
670
|
+
| `absent` | a dir, config file or key the row expects is not there (an MCP file never created, a missing `mcpServers` key) -- informational, exit `0` |
|
|
671
|
+
| `note` | worth knowing: shared dir, orphan backup, JSONC `openclaw.json` / `opencode.json`, a `--harness` that is not installed |
|
|
672
|
+
| `unverified` | no version could be read, or the row has nothing to check |
|
|
673
|
+
| `warn` | loose file modes; a parked item or companion file with no state entry; a leftover `parked/<sha8>` dir that still holds files (an empty one is ignored); an op a killed run left in flight that the next change settles (`pending: done` / `undone`) |
|
|
674
|
+
| `error` | needs fixing: a config that exists but is unparseable or unsupported (`layout changed`), a state entry whose files (companions included) are gone or fail the tamper checks, a companion both live and parked, a flag re-enabled outside the tool, an op a killed run left in flight that needs you (`pending: stuck`, with the exact fix) |
|
|
675
|
+
|
|
676
|
+
Only `error` makes the exit code `1`; each problem row names the command that
|
|
677
|
+
fixes it. `--harness X` for a harness that is not installed is a `note` (exit
|
|
678
|
+
`0`).
|
|
679
|
+
|
|
680
|
+
A harness item parked with no state entry also carries `orphan`, and its fix
|
|
681
|
+
follows from it: `identical` (the live copy has the same content: delete the
|
|
682
|
+
parked copy), `differs` (a different live copy exists: compare, keep one), or
|
|
683
|
+
`parked-only` (move it back, then `disable` it so the state records it).
|
|
684
|
+
|
|
685
|
+
### Fixing
|
|
686
|
+
|
|
687
|
+
`install-shims` (and so `./install.sh`) runs the same check silently after
|
|
688
|
+
writing the shims and asks only about these fixable problems, each shown first.
|
|
689
|
+
|
|
690
|
+
On a terminal (stdin and stdout both a TTY, no `--json`) doctor then asks
|
|
691
|
+
`(y/n)` for each problem it can fix without a judgment call, and runs each yes
|
|
692
|
+
under the run lock; anything else keeps its `fix:` hint for you. A fixed row's
|
|
693
|
+
status becomes `fixed` and no longer counts toward the exit code; a failed fix
|
|
694
|
+
adds an `error` row. Piped, `--json`, or run by an agent through a shim, doctor
|
|
695
|
+
stays read-only.
|
|
696
|
+
|
|
697
|
+
| problem | fix it offers |
|
|
698
|
+
|---|---|
|
|
699
|
+
| loose mode on the state dir, `state.json` or a backup | `chmod 700` / `chmod 600` |
|
|
700
|
+
| backup with no state entry, server configured again in its harness | delete the backup (not offered while the server is missing, nor for project backups) |
|
|
701
|
+
| flag re-enabled outside the tool | `enable` the item, which clears the stale entry |
|
|
702
|
+
| origin dir gone | recreate it, then `enable` the item |
|
|
703
|
+
| op a killed run left in flight (`pending: done` / `undone`) | settle it now, as the next change would |
|
|
704
|
+
| orphan parked item, `identical` | delete the parked copy |
|
|
705
|
+
| orphan parked item, `parked-only` | move it back (restores it) |
|
|
706
|
+
|
|
707
|
+
Not offered: `differs` orphans, missing parked items or backups, a gone project
|
|
708
|
+
dir, tamper refusals, `pending: stuck`, companion conflicts, unparseable configs.
|
|
709
|
+
|
|
710
|
+
## Assumed formats
|
|
711
|
+
|
|
712
|
+
Two config shapes came from the design survey (`docs/DESIGN.md` §4 / §11). Both
|
|
713
|
+
were since seen on one real install each (openclaw 2026.7.1-2, opencode 2.0.22; see
|
|
714
|
+
`docs/harnesses.md`), but the tool still treats them defensively:
|
|
715
|
+
|
|
716
|
+
- `openclaw.json`: `skills.entries.<name>.enabled` and
|
|
717
|
+
`plugins.entries.<name>.enabled`
|
|
718
|
+
- `opencode.json`: `mcp.<name>.enabled`
|
|
719
|
+
|
|
720
|
+
The edit changes one boolean token and nothing else, is verified after writing
|
|
721
|
+
and rolled back on any mismatch (bytes and file mode). JSONC (`//` and `/* */`
|
|
722
|
+
comments, trailing commas) is edited the same way: only the flag token changes,
|
|
723
|
+
comments and commas are kept. JSON5 (unquoted keys, single quotes, hex) and files
|
|
724
|
+
that repeat a key are **refused**, never rewritten; a leading BOM is kept as is; a
|
|
725
|
+
missing key is refused, never invented. An openclaw skill
|
|
726
|
+
uses the flag only when `skills.entries.<name>` already exists, otherwise its
|
|
727
|
+
directory is moved. The state entry (with the flag's previous value) is saved
|
|
728
|
+
before the flag is written, so a run killed in between is settled by the next
|
|
729
|
+
run, never guessed (see "Where state lives"). Please report a real install that
|
|
730
|
+
differs.
|
|
731
|
+
|
|
732
|
+
## Safety checks
|
|
733
|
+
|
|
734
|
+
- `enable` refuses a state entry whose `origin`, `parked_at`, backup or flag
|
|
735
|
+
file is outside its harness home, project or `~/.agent-toggle`, or holds `..`:
|
|
736
|
+
an error row `refused: <reason>`, nothing moved.
|
|
737
|
+
- Every edit of a JSON or TOML config is verified after writing (still parses,
|
|
738
|
+
only the target changed) and rolled back, bytes and mode, on failure. An
|
|
739
|
+
invalid codex `config.toml` makes an MCP edit fail and roll back instead of
|
|
740
|
+
being rewritten. A codex `config.toml` edit also fails (and is left as the other
|
|
741
|
+
tool wrote it) if the file changed between read and write, and CRLF files keep their
|
|
742
|
+
line endings. On Python 3.10 (no `tomllib`) the edit is parse-checked by a stdlib structural
|
|
743
|
+
validator (`agent_toggle/toml_check.py`, differentially tested against `tomllib`),
|
|
744
|
+
on top of the textual check that only the one block changed.
|
|
745
|
+
- Profiles and project dirs are validated like command-line input.
|
|
746
|
+
|
|
747
|
+
## Where state lives
|
|
748
|
+
|
|
749
|
+
All of it in `~/.agent-toggle/` (mode `0700`), never as marker files next to the targets —
|
|
750
|
+
your `git status` in your own project must not change because of our
|
|
751
|
+
bookkeeping.
|
|
752
|
+
|
|
753
|
+
| file | contents |
|
|
754
|
+
|---|---|
|
|
755
|
+
| `state.json` | current disabled list (schema v3; atomic write, mode `0600`), saved before each flag write or dir move with the op in flight under an optional `pending` key, and after each MCP / plugin item |
|
|
756
|
+
| `lock` | held by `disable` / `enable` / `enable --all` / `undo` / `profile apply` / `migrate` / `ui` for the whole batch; a second run waits 5 s then exits `3` (stale after 10 min *and* its PID is gone; a live batch refreshes it per item; the error says when its PID is gone, i.e. a killed run left it) |
|
|
757
|
+
| `log.jsonl` | one line per operation (mode `0600`), see below |
|
|
758
|
+
| `mcp-backups/` | `<harness>__<server>.json`, or `<sha8>__<harness>__<server>.json` for a project `.mcp.json` (mode `0600` -- may hold auth headers) |
|
|
759
|
+
| `companions/` | parked exclusive helper files |
|
|
760
|
+
| `parked/<sha8>/` | items parked by `--project` (`<sha8>` = first 8 hex of the SHA-1 of the resolved project dir) |
|
|
761
|
+
| `profiles/` | `<name>.json` profiles (dir `0700`, files `0600`) |
|
|
762
|
+
| `config.json` | [settings](#settings--config-menu) and the Telegram chat id (mode `0600`, atomic write, unknown keys kept) |
|
|
763
|
+
| `update-check.json` | the [update check](#update-check)'s 10-minute cache (mode `0600`, atomic write) |
|
|
764
|
+
|
|
765
|
+
A killed run loses at most the one item in flight. If that was a flag write or a
|
|
766
|
+
dir move (with its companions), its `pending` record (the full entry, a flag's
|
|
767
|
+
previous value included) lets the next `disable` / `enable` / `enable --all` /
|
|
768
|
+
`undo` / ... finish it (it reached disk: recorded) or drop it (it did not), with
|
|
769
|
+
a warning and a `recovered` log row (which `undo` does not reverse). `status` and
|
|
770
|
+
`doctor` report it, with `agent-toggle enable ...` when the item ends up
|
|
771
|
+
disabled. The one case left to you is a cross-filesystem move killed between its
|
|
772
|
+
two renames: both copies are complete, and the report names the `diff -r` to
|
|
773
|
+
check and the `rm -rf` that keeps either one. MCP and plugin items are saved
|
|
774
|
+
per item (when the next one starts), without a `pending` record (a project `.mcp.json`
|
|
775
|
+
edit has one): a kill inside that window
|
|
776
|
+
leaves the change unrecorded (an MCP server's backup stays in `mcp-backups/`).
|
|
777
|
+
A killed run also leaves its `lock`; the next run's error says so.
|
|
778
|
+
|
|
779
|
+
Each `log.jsonl` row is `{ts, harness, type, name, action, result, batch,
|
|
780
|
+
project, scope, detail}`. `batch` is one id per run (what `undo` reverses);
|
|
781
|
+
`project` is `null` and `scope` is `user` outside project scope (a claude
|
|
782
|
+
local-scope MCP row has `scope: local` and its working directory in `project`).
|
|
783
|
+
Older rows without `batch`/`harness` cannot be undone.
|
|
784
|
+
|
|
785
|
+
## Two guardrails, both earned
|
|
786
|
+
|
|
787
|
+
**1. `safe_move()` never lets the source be renamed into the destination.**
|
|
788
|
+
`mv X dest/` when `dest` does not exist *renames* `X` to `dest` — the first
|
|
789
|
+
item fails, the second "succeeds" by becoming that directory, and `SKILL.md`
|
|
790
|
+
and `.git` end up scattered at the root. `shutil.move()` behaves identically.
|
|
791
|
+
So: create the directory, prove it *is* a directory, prove the target does not
|
|
792
|
+
exist, and only then touch the source.
|
|
793
|
+
|
|
794
|
+
Order matters too: `mkdir(exist_ok=True)` raises `FileExistsError` when the
|
|
795
|
+
path exists as a *file*, so the `is_dir()` check has to come **before** the
|
|
796
|
+
`mkdir` or it is unreachable.
|
|
797
|
+
|
|
798
|
+
**2. Park dirs that are not gitignored get a warning.** Without it, every
|
|
799
|
+
disable leaves dozens of deletion lines in `git status`. The warning is shown once
|
|
800
|
+
per park dir, and only for a dir inside a git work tree (elsewhere `git status`
|
|
801
|
+
cannot be dirtied and the `.gitignore` fix would not apply).
|
|
802
|
+
|
|
803
|
+
## Paths are resolved before comparison
|
|
804
|
+
|
|
805
|
+
`~/.claude` is often a symlink, and on macOS `/var` is one. Comparing
|
|
806
|
+
unresolved paths makes every companion look like it lives outside the harness.
|
|
807
|
+
Both sides are resolved first.
|
|
808
|
+
|
|
809
|
+
## Design and roadmap
|
|
810
|
+
|
|
811
|
+
Architecture, harness survey, cost model, known gaps and the phased roadmap
|
|
812
|
+
toward a public multi-OS release live in `docs/DESIGN.md`. Release notes are in
|
|
813
|
+
`CHANGELOG.md`.
|
|
814
|
+
|
|
815
|
+
## Tests
|
|
816
|
+
|
|
817
|
+
```sh
|
|
818
|
+
python3 -m unittest discover -s tests -v
|
|
819
|
+
ruff check .
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
stdlib `unittest`, no fixtures, no network. Every test runs against a
|
|
823
|
+
throwaway temp `HOME` and a stubbed `claude` CLI.
|
|
824
|
+
|
|
825
|
+
## CI notifications
|
|
826
|
+
|
|
827
|
+
`.github/workflows/ci.yml` can send a Telegram message when the test matrix fails on a
|
|
828
|
+
push (a lint-only failure does not page). Alerts never fire for pull requests or green
|
|
829
|
+
runs. The message names the repository, branch, 7-character commit and a link to the run.
|
|
830
|
+
|
|
831
|
+
Set it up once, with the `telegram` extra installed (see [Install](#install)):
|
|
832
|
+
|
|
833
|
+
```sh
|
|
834
|
+
agent-toggle config # settings menu: Notifications has the bot token (hidden) and chat id
|
|
835
|
+
agent-toggle config test # send one test message
|
|
836
|
+
agent-toggle config sync-ci # set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID on the repo
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
In the [settings menu](#settings--config-menu) an inline field commits on `Enter`, cancels on
|
|
840
|
+
`Esc`, and clears on `-` then `Enter`; the Tools group also has Send test message and Sync CI
|
|
841
|
+
secrets (the same as `config test` / `config sync-ci`, and they need the extra). The bot token
|
|
842
|
+
is kept only in the OS credential store (macOS Keychain, Linux Secret Service, Windows DPAPI)
|
|
843
|
+
through telegram-kit, never in a file or on a command line, and is shown masked. With no
|
|
844
|
+
credential store it refuses to store the token; set `TG_BOT_TOKEN` in the environment instead
|
|
845
|
+
(`TG_CHAT_ID` likewise for the chat id). The chat id (`telegram_chat_id`) is ordinary
|
|
846
|
+
configuration in `~/.agent-toggle/config.json`: a number, `-100...` for a group, or an
|
|
847
|
+
`@channel`. `config sync-ci` needs the GitHub CLI (`gh`) signed in; it passes both values on
|
|
848
|
+
stdin, and `--repo OWNER/REPO` / `--dry-run` work as elsewhere.
|
|
849
|
+
|
|
850
|
+
To skip the tool, set the secrets by hand (each command prompts for the value):
|
|
851
|
+
|
|
852
|
+
```sh
|
|
853
|
+
gh secret set TELEGRAM_BOT_TOKEN
|
|
854
|
+
gh secret set TELEGRAM_CHAT_ID
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
With either secret unset (a fork, or a clone you have not configured) the notify job
|
|
858
|
+
prints a `::notice::` and exits 0, so it never adds a second red X.
|
|
859
|
+
|
|
860
|
+
## Releasing
|
|
861
|
+
|
|
862
|
+
A release is a git tag `vX.Y.Z` (where `X.Y.Z` must match `agent_toggle.__version__`).
|
|
863
|
+
Pushing the tag triggers `.github/workflows/release.yml`:
|
|
864
|
+
|
|
865
|
+
1. **Build** job checks that the tag matches `__version__`, builds a wheel and
|
|
866
|
+
source distribution, and generates the shell completion files (uploaded as the
|
|
867
|
+
`completions` artifact; the workflow does not create a GitHub Release).
|
|
868
|
+
2. **Publish** job uploads to PyPI using
|
|
869
|
+
[trusted publishing](https://docs.pypi.org/trusted-publishers/),
|
|
870
|
+
with no stored tokens—only the `pypi` environment and OIDC setup.
|
|
871
|
+
3. **Smoke** job installs the published version on macOS under a throwaway `HOME`
|
|
872
|
+
and runs a `disable` / `enable` round trip to verify the install.
|
|
873
|
+
|
|
874
|
+
Before the first tag, register a trusted publisher for this repository with
|
|
875
|
+
PyPI: workflow `release.yml`, environment `pypi`. See
|
|
876
|
+
[PyPI trusted publishers documentation](https://docs.pypi.org/trusted-publishers/).
|
|
877
|
+
|
|
878
|
+
## Cross-machine behaviour
|
|
879
|
+
|
|
880
|
+
Disabling is a **local** decision: park dirs are gitignored and do not sync.
|
|
881
|
+
But a disappearance under `skills/` is itself a tracked change, so committing
|
|
882
|
+
it means other machines lose those skills on pull. The content stays in git
|
|
883
|
+
history — `git checkout <commit> -- skills/<name>` brings it back. Don't
|
|
884
|
+
commit those deletions if you don't want them to travel.
|