agent-toggle 0.7.3__tar.gz → 0.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/PKG-INFO +43 -11
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/README.md +42 -10
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/__init__.py +1 -1
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/cli.py +35 -16
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/cost.py +28 -4
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/help.py +4 -2
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/locales/zh_tw_cli.py +1 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/locales/zh_tw_picker.py +2 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/settings.py +1 -1
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/shims/claude.md.tmpl +4 -1
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/shims/generic.md.tmpl +1 -1
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ui/menu.py +5 -1
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ui/model.py +28 -8
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ui/picker.py +9 -3
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle.egg-info/PKG-INFO +43 -11
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle.egg-info/SOURCES.txt +3 -0
- agent_toggle-0.8.1/tests/test_all_type.py +74 -0
- agent_toggle-0.8.1/tests/test_mods.py +208 -0
- agent_toggle-0.8.1/tests/test_same_name.py +48 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/LICENSE +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/__main__.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/backends/__init__.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/backends/flag_json.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/backends/mcp_json.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/backends/mcp_toml.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/backends/plugin_cli.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/companions.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/config.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/doctor.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/fs.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/harnesses.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/i18n.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/locales/__init__.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/locales/zh_tw_common.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/locales/zh_tw_config.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/mechanisms.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ops.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/output.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/profiles.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/spinner.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/store.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/toml_check.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ui/__init__.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ui/config_menu.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/ui/theme.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/undo.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/update_check.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle/update_prompt.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle.egg-info/dependency_links.txt +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle.egg-info/entry_points.txt +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle.egg-info/requires.txt +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/agent_toggle.egg-info/top_level.txt +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/pyproject.toml +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/setup.cfg +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_alias.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_backends.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_cli.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_cli_surface.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_color.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_companions.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_completions.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_config.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_config_menu.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_conformance.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_containment.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_cost.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_crash.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_doctor.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_encoding.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_fs.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_harnesses.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_help.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_i18n.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_install.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_mechanisms.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_menu.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_picker.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_platform.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_profiles.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_project.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_safety.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_secret_audit.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_settings.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_store.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_theme.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_undo.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_update_check.py +0 -0
- {agent_toggle-0.7.3 → agent_toggle-0.8.1}/tests/test_update_prompt.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-toggle
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Temporarily disable / re-enable AI-agent skills, agents, commands, rules, plugins and MCP servers across harnesses (Claude Code, Codex, Grok, OpenCode, OpenClaw, Copilot, Vibe, Devin, Antigravity)
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/weskao/agent-toggle
|
|
@@ -33,7 +33,7 @@ Dynamic: license-file
|
|
|
33
33
|
# agent-toggle
|
|
34
34
|
|
|
35
35
|
Temporarily disable and restore AI-agent resources — skills, agents, commands,
|
|
36
|
-
rules, plugins, MCP servers — across Claude Code, Codex, Grok CLI, OpenCode,
|
|
36
|
+
rules, plugins (mods included), MCP servers — across Claude Code, Codex, Grok CLI, OpenCode,
|
|
37
37
|
OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at
|
|
38
38
|
session start.
|
|
39
39
|
|
|
@@ -154,13 +154,14 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
|
|
|
154
154
|
|
|
155
155
|
| command | what it does |
|
|
156
156
|
|---|---|
|
|
157
|
-
| `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) |
|
|
157
|
+
| `ui` (alias `pick`) | interactive picker — harness tabs, type groups (plus a Mods group), 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) |
|
|
158
158
|
| `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`) |
|
|
159
159
|
| `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
|
|
160
160
|
| `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) |
|
|
161
161
|
| `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)) |
|
|
162
162
|
| `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
|
|
163
163
|
| `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
|
|
164
|
+
| `disable\|enable all <name>...` | every type on the harness with that name at once (skill, command, MCP, ...); a name with no match is an `error` row; one batch, so `undo` reverses it |
|
|
164
165
|
| `enable --all` | put back **every** disabled item (`--harness H` narrows it, `--project <dir>` takes only that project's) |
|
|
165
166
|
| `undo` | reverse the last logged batch (`--dry-run` shows the plan) |
|
|
166
167
|
| `profile save\|apply\|diff\|list` | named sets of live items; see [Profiles](#profiles) |
|
|
@@ -174,6 +175,8 @@ Every command also works with a leading `--` (`agent-toggle --status`, `--help`,
|
|
|
174
175
|
|
|
175
176
|
`<type>` = `skill` / `agent` / `command` / `rule` / `plugin` / `mcp`.
|
|
176
177
|
`rule` is claude-only (`~/.claude/rules/*.md`, parked in `~/.agent-toggle/parked/user/claude/rules/`).
|
|
178
|
+
A **mod** (a claude plugin that loads UI code, see [Mods](#mods)) is type `plugin` on the
|
|
179
|
+
command line: `disable plugin blast-radius@example-marketplace`.
|
|
177
180
|
Disabling a rule prints a warning (also in `--json` `warnings`, dry run included)
|
|
178
181
|
that rules may carry safety constraints.
|
|
179
182
|
|
|
@@ -237,6 +240,7 @@ agent-toggle disable skill demo-skill other-skill
|
|
|
237
240
|
agent-toggle disable command demo:batch # nested commands/demo/batch.md
|
|
238
241
|
agent-toggle disable agent demo-agent
|
|
239
242
|
agent-toggle disable mcp example-mcp
|
|
243
|
+
agent-toggle disable all example-tool # skill, command, mcp... named example-tool
|
|
240
244
|
agent-toggle disable skill demo-skill --harness codex
|
|
241
245
|
agent-toggle enable mcp example-mcp
|
|
242
246
|
agent-toggle cost --type skill --json
|
|
@@ -273,12 +277,14 @@ What is on screen:
|
|
|
273
277
|
- **Title bar**: version and a summary (resources, live tokens, staged changes and their token effect).
|
|
274
278
|
- **Harness tab bar**: `All` plus each enabled harness, with counts. Harnesses switched off in
|
|
275
279
|
the [settings](#settings--config-menu) are hidden.
|
|
276
|
-
- **Rows grouped by type** (Skills, Agents, Commands,
|
|
280
|
+
- **Rows grouped by type** (Skills, Agents, Commands, Rules, Plugins, Mods, MCP) under a heading
|
|
281
|
+
with a count.
|
|
277
282
|
`●` is live, `○` parked (`*` / `o` in ASCII); a `+` / `-` before the name marks a staged change,
|
|
278
283
|
and `+shared` marks a directory another harness also reads.
|
|
279
284
|
- **Cost column**: estimated startup tokens (chars / 4, about +-25 %) with a colored bar. A parked
|
|
280
285
|
row shows `(N)` in brackets, what restoring it would load. Plugins appear as rows too (via
|
|
281
286
|
`claude plugin list --json`; skipped under `ui --dry-run`, which never shells out).
|
|
287
|
+
Mods get their own **Mods** heading right after Plugins (see [Mods](#mods)).
|
|
282
288
|
- **Detail pane** at 100 columns or wider: the file's frontmatter `description` (skills,
|
|
283
289
|
agents, commands, rules), then harness, type, state, staged, path, cost, since,
|
|
284
290
|
mechanism, and what it is shared with.
|
|
@@ -296,7 +302,7 @@ What is on screen:
|
|
|
296
302
|
| `Enter` | apply every staged change (with `--dry-run`: show the plan) |
|
|
297
303
|
| `Esc` / `Ctrl-C` | cancel; nothing is applied |
|
|
298
304
|
| `↑` `↓` `PgUp` `PgDn` `Home` `End`, `Ctrl-P` / `Ctrl-N` | move |
|
|
299
|
-
| `/` | 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` |
|
|
305
|
+
| `/` | start a search; any other non-command letter starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name, the group (`mod` or `mods` finds every mod) and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
|
|
300
306
|
| `Backspace` / `Ctrl-U` | delete one character / clear the filter |
|
|
301
307
|
| `←` `→` | switch harness tab (also while typing a filter); `0` = All, `1`-`9` = that tab, `h` = next tab |
|
|
302
308
|
| `t` | cycle the type filter |
|
|
@@ -304,8 +310,9 @@ What is on screen:
|
|
|
304
310
|
| `p` | profiles: stage a saved one, or save the live state (see below) |
|
|
305
311
|
| `?` | show the key list |
|
|
306
312
|
| `a` / `Ctrl-A` | toggle every visible row |
|
|
313
|
+
| `n` | toggle every row on this row's harness with the same name, of any type (skill, command, MCP, ...), shown or filtered out; like `disable all <name>` |
|
|
307
314
|
|
|
308
|
-
The command keys (`t`, `s`, `p`, `h`, `a`, `0`-`9`, `?`) act only while no filter is active.
|
|
315
|
+
The command keys (`t`, `s`, `p`, `h`, `a`, `n`, `0`-`9`, `?`) act only while no filter is active.
|
|
309
316
|
Press `/` first to type a filter that begins with one of them; once the filter is non-empty,
|
|
310
317
|
every letter just types.
|
|
311
318
|
|
|
@@ -331,11 +338,35 @@ Built on stdlib `curses`, so there is nothing to install on macOS and Linux
|
|
|
331
338
|
(on Windows, `pip install "agent-toggle[windows]"` pulls `windows-curses`).
|
|
332
339
|
Without curses, `ui` falls back to a numbered menu with the same staging and the
|
|
333
340
|
same result. It is grouped by type, with the same glyphs and color: type row
|
|
334
|
-
numbers (`1 3 5-7`) to tick or untick,
|
|
341
|
+
numbers (`1 3 5-7`) to tick or untick, `n <number>` to toggle every type with that
|
|
342
|
+
row's name, `/text` to filter (`/` alone clears it),
|
|
335
343
|
`s` / `h` / `t` to sort and cycle the harness and type filters, `p` to list profiles,
|
|
336
344
|
`p <number|name>` to stage one and `p save <name>` to save the live state, `a` to
|
|
337
345
|
apply, `q` (or end of input) to cancel.
|
|
338
346
|
|
|
347
|
+
### Mods
|
|
348
|
+
|
|
349
|
+
A mod is a claude plugin whose `hooks/hooks.json` has a top-level `modules` key, for example
|
|
350
|
+
`{ "modules": ["./register.tsx"] }`: it loads `.mjs` / `.tsx` code that can draw UI (a pane,
|
|
351
|
+
a band above the prompt, buttons) and change Claude Code's behaviour. A plugin with no
|
|
352
|
+
`hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
|
|
353
|
+
plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
|
|
354
|
+
|
|
355
|
+
- The check reads the install path `claude plugin list` reports and every cached version
|
|
356
|
+
under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
|
|
357
|
+
To list the installed mods by hand:
|
|
358
|
+
|
|
359
|
+
```sh
|
|
360
|
+
grep -l modules ~/.claude/plugins/cache/*/*/*/hooks/hooks.json
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
- The picker lists mods under **Mods**, right after Plugins. `t` cycles to it, the
|
|
364
|
+
`picker_type` setting can open on it, and a search for `mod` or `mods` finds every mod.
|
|
365
|
+
The detail pane shows its type as `mod (plugin)`.
|
|
366
|
+
- A mod is still a plugin everywhere else: it toggles through `claude plugin
|
|
367
|
+
enable/disable`, `disable plugin <id>` reaches it, profiles store it as `plugin`, and
|
|
368
|
+
`list` / `cost` show it with the plugins (`cost --json` marks it `"mod": true`).
|
|
369
|
+
|
|
339
370
|
The chrome (title, tabs, footer, help, detail labels) is translated when `language` is
|
|
340
371
|
`zh-TW`; item names and paths are never translated.
|
|
341
372
|
|
|
@@ -377,7 +408,7 @@ tool does not know are kept):
|
|
|
377
408
|
| `harness.<name>` | `true` / `false`, for `claude` `codex` `grok` `opencode` `openclaw` `copilot` `vibe` `devin` `agy` | `true` | |
|
|
378
409
|
| `picker_sort` | `name` / `cost` | `name` | |
|
|
379
410
|
| `picker_harness` | `all` or a harness name | `all` | |
|
|
380
|
-
| `picker_type` | `all
|
|
411
|
+
| `picker_type` | `all`, a type, or `mod` | `all` | |
|
|
381
412
|
| `telegram_chat_id` | a number, `-100...` or `@channel` | unset | `TG_CHAT_ID` |
|
|
382
413
|
|
|
383
414
|
The bot token is not in this file: it is in the OS keystore through telegram-kit
|
|
@@ -432,7 +463,7 @@ translated when `language` is `zh-TW`.
|
|
|
432
463
|
|
|
433
464
|
| harness | home | skill | agent | command | rule | plugin | mcp |
|
|
434
465
|
|---|---|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
435
|
-
| claude | `~/.claude` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ `~/.claude.json` |
|
|
466
|
+
| claude | `~/.claude` | ✓ | ✓ | ✓ | ✓ | ✓ (mods too) | ✓ `~/.claude.json` |
|
|
436
467
|
| codex | `~/.codex` | ✓ | ✓ | ✓ (`commands/` + `prompts/`) | — | — | ✓ `config.toml` |
|
|
437
468
|
| grok | `~/.grok` | ✓ | — | — | — | — | ✓ `config.toml` |
|
|
438
469
|
| opencode | `$XDG_CONFIG_HOME/opencode`, else `~/.config/opencode` | ✓ | — | ✓ (`command/`) | — | — | ✓ `opencode.json` (flag, *assumed*) |
|
|
@@ -842,8 +873,9 @@ Both sides are resolved first.
|
|
|
842
873
|
## Design and roadmap
|
|
843
874
|
|
|
844
875
|
Architecture, harness survey, cost model, known gaps and the phased roadmap
|
|
845
|
-
toward a public multi-OS release live in `docs/DESIGN.md`.
|
|
846
|
-
|
|
876
|
+
toward a public multi-OS release live in `docs/DESIGN.md`. The design for the
|
|
877
|
+
Claude Code pane mod (the picker inside Claude, driven by this CLI) is in
|
|
878
|
+
`docs/MOD.md`. Release notes are in `CHANGELOG.md`.
|
|
847
879
|
|
|
848
880
|
## Tests
|
|
849
881
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# agent-toggle
|
|
2
2
|
|
|
3
3
|
Temporarily disable and restore AI-agent resources — skills, agents, commands,
|
|
4
|
-
rules, plugins, MCP servers — across Claude Code, Codex, Grok CLI, OpenCode,
|
|
4
|
+
rules, plugins (mods included), MCP servers — across Claude Code, Codex, Grok CLI, OpenCode,
|
|
5
5
|
OpenClaw, Copilot, Vibe, Devin and Antigravity — and show what each one costs at
|
|
6
6
|
session start.
|
|
7
7
|
|
|
@@ -122,13 +122,14 @@ agent-toggle <command> [args] # or: python3 agent_toggle.py <command> [
|
|
|
122
122
|
|
|
123
123
|
| command | what it does |
|
|
124
124
|
|---|---|
|
|
125
|
-
| `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) |
|
|
125
|
+
| `ui` (alias `pick`) | interactive picker — harness tabs, type groups (plus a Mods group), 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) |
|
|
126
126
|
| `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`) |
|
|
127
127
|
| `list [type]` | what is currently disabled (`--project <dir>` filters to one project) |
|
|
128
128
|
| `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) |
|
|
129
129
|
| `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)) |
|
|
130
130
|
| `disable <type> <name>...` | park one or more items (`--dry-run` shows the plan; `--project <dir>` for a repo's own `.claude/` and `.mcp.json`) |
|
|
131
131
|
| `enable <type> <name>...` | put them back (`--dry-run` shows the plan; `--project <dir>` likewise) |
|
|
132
|
+
| `disable\|enable all <name>...` | every type on the harness with that name at once (skill, command, MCP, ...); a name with no match is an `error` row; one batch, so `undo` reverses it |
|
|
132
133
|
| `enable --all` | put back **every** disabled item (`--harness H` narrows it, `--project <dir>` takes only that project's) |
|
|
133
134
|
| `undo` | reverse the last logged batch (`--dry-run` shows the plan) |
|
|
134
135
|
| `profile save\|apply\|diff\|list` | named sets of live items; see [Profiles](#profiles) |
|
|
@@ -142,6 +143,8 @@ Every command also works with a leading `--` (`agent-toggle --status`, `--help`,
|
|
|
142
143
|
|
|
143
144
|
`<type>` = `skill` / `agent` / `command` / `rule` / `plugin` / `mcp`.
|
|
144
145
|
`rule` is claude-only (`~/.claude/rules/*.md`, parked in `~/.agent-toggle/parked/user/claude/rules/`).
|
|
146
|
+
A **mod** (a claude plugin that loads UI code, see [Mods](#mods)) is type `plugin` on the
|
|
147
|
+
command line: `disable plugin blast-radius@example-marketplace`.
|
|
145
148
|
Disabling a rule prints a warning (also in `--json` `warnings`, dry run included)
|
|
146
149
|
that rules may carry safety constraints.
|
|
147
150
|
|
|
@@ -205,6 +208,7 @@ agent-toggle disable skill demo-skill other-skill
|
|
|
205
208
|
agent-toggle disable command demo:batch # nested commands/demo/batch.md
|
|
206
209
|
agent-toggle disable agent demo-agent
|
|
207
210
|
agent-toggle disable mcp example-mcp
|
|
211
|
+
agent-toggle disable all example-tool # skill, command, mcp... named example-tool
|
|
208
212
|
agent-toggle disable skill demo-skill --harness codex
|
|
209
213
|
agent-toggle enable mcp example-mcp
|
|
210
214
|
agent-toggle cost --type skill --json
|
|
@@ -241,12 +245,14 @@ What is on screen:
|
|
|
241
245
|
- **Title bar**: version and a summary (resources, live tokens, staged changes and their token effect).
|
|
242
246
|
- **Harness tab bar**: `All` plus each enabled harness, with counts. Harnesses switched off in
|
|
243
247
|
the [settings](#settings--config-menu) are hidden.
|
|
244
|
-
- **Rows grouped by type** (Skills, Agents, Commands,
|
|
248
|
+
- **Rows grouped by type** (Skills, Agents, Commands, Rules, Plugins, Mods, MCP) under a heading
|
|
249
|
+
with a count.
|
|
245
250
|
`●` is live, `○` parked (`*` / `o` in ASCII); a `+` / `-` before the name marks a staged change,
|
|
246
251
|
and `+shared` marks a directory another harness also reads.
|
|
247
252
|
- **Cost column**: estimated startup tokens (chars / 4, about +-25 %) with a colored bar. A parked
|
|
248
253
|
row shows `(N)` in brackets, what restoring it would load. Plugins appear as rows too (via
|
|
249
254
|
`claude plugin list --json`; skipped under `ui --dry-run`, which never shells out).
|
|
255
|
+
Mods get their own **Mods** heading right after Plugins (see [Mods](#mods)).
|
|
250
256
|
- **Detail pane** at 100 columns or wider: the file's frontmatter `description` (skills,
|
|
251
257
|
agents, commands, rules), then harness, type, state, staged, path, cost, since,
|
|
252
258
|
mechanism, and what it is shared with.
|
|
@@ -264,7 +270,7 @@ What is on screen:
|
|
|
264
270
|
| `Enter` | apply every staged change (with `--dry-run`: show the plan) |
|
|
265
271
|
| `Esc` / `Ctrl-C` | cancel; nothing is applied |
|
|
266
272
|
| `↑` `↓` `PgUp` `PgDn` `Home` `End`, `Ctrl-P` / `Ctrl-N` | move |
|
|
267
|
-
| `/` | 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` |
|
|
273
|
+
| `/` | start a search; any other non-command letter starts one too. Terms are ANDed, case-insensitive, and match the harness/type/name, the group (`mod` or `mods` finds every mod) and the path; when nothing matches, letters-in-order (fuzzy) is tried: `ctxmd` finds `context-md`, and the bar says `≈ fuzzy match` |
|
|
268
274
|
| `Backspace` / `Ctrl-U` | delete one character / clear the filter |
|
|
269
275
|
| `←` `→` | switch harness tab (also while typing a filter); `0` = All, `1`-`9` = that tab, `h` = next tab |
|
|
270
276
|
| `t` | cycle the type filter |
|
|
@@ -272,8 +278,9 @@ What is on screen:
|
|
|
272
278
|
| `p` | profiles: stage a saved one, or save the live state (see below) |
|
|
273
279
|
| `?` | show the key list |
|
|
274
280
|
| `a` / `Ctrl-A` | toggle every visible row |
|
|
281
|
+
| `n` | toggle every row on this row's harness with the same name, of any type (skill, command, MCP, ...), shown or filtered out; like `disable all <name>` |
|
|
275
282
|
|
|
276
|
-
The command keys (`t`, `s`, `p`, `h`, `a`, `0`-`9`, `?`) act only while no filter is active.
|
|
283
|
+
The command keys (`t`, `s`, `p`, `h`, `a`, `n`, `0`-`9`, `?`) act only while no filter is active.
|
|
277
284
|
Press `/` first to type a filter that begins with one of them; once the filter is non-empty,
|
|
278
285
|
every letter just types.
|
|
279
286
|
|
|
@@ -299,11 +306,35 @@ Built on stdlib `curses`, so there is nothing to install on macOS and Linux
|
|
|
299
306
|
(on Windows, `pip install "agent-toggle[windows]"` pulls `windows-curses`).
|
|
300
307
|
Without curses, `ui` falls back to a numbered menu with the same staging and the
|
|
301
308
|
same result. It is grouped by type, with the same glyphs and color: type row
|
|
302
|
-
numbers (`1 3 5-7`) to tick or untick,
|
|
309
|
+
numbers (`1 3 5-7`) to tick or untick, `n <number>` to toggle every type with that
|
|
310
|
+
row's name, `/text` to filter (`/` alone clears it),
|
|
303
311
|
`s` / `h` / `t` to sort and cycle the harness and type filters, `p` to list profiles,
|
|
304
312
|
`p <number|name>` to stage one and `p save <name>` to save the live state, `a` to
|
|
305
313
|
apply, `q` (or end of input) to cancel.
|
|
306
314
|
|
|
315
|
+
### Mods
|
|
316
|
+
|
|
317
|
+
A mod is a claude plugin whose `hooks/hooks.json` has a top-level `modules` key, for example
|
|
318
|
+
`{ "modules": ["./register.tsx"] }`: it loads `.mjs` / `.tsx` code that can draw UI (a pane,
|
|
319
|
+
a band above the prompt, buttons) and change Claude Code's behaviour. A plugin with no
|
|
320
|
+
`hooks.json`, or with only ordinary hooks (shell commands such as `PreToolUse`), is a plain
|
|
321
|
+
plugin: it ships skills, commands, agents, MCP servers or hooks and draws nothing.
|
|
322
|
+
|
|
323
|
+
- The check reads the install path `claude plugin list` reports and every cached version
|
|
324
|
+
under `~/.claude/plugins/cache/<marketplace>/<name>/`, so a parked mod is still a mod.
|
|
325
|
+
To list the installed mods by hand:
|
|
326
|
+
|
|
327
|
+
```sh
|
|
328
|
+
grep -l modules ~/.claude/plugins/cache/*/*/*/hooks/hooks.json
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- The picker lists mods under **Mods**, right after Plugins. `t` cycles to it, the
|
|
332
|
+
`picker_type` setting can open on it, and a search for `mod` or `mods` finds every mod.
|
|
333
|
+
The detail pane shows its type as `mod (plugin)`.
|
|
334
|
+
- A mod is still a plugin everywhere else: it toggles through `claude plugin
|
|
335
|
+
enable/disable`, `disable plugin <id>` reaches it, profiles store it as `plugin`, and
|
|
336
|
+
`list` / `cost` show it with the plugins (`cost --json` marks it `"mod": true`).
|
|
337
|
+
|
|
307
338
|
The chrome (title, tabs, footer, help, detail labels) is translated when `language` is
|
|
308
339
|
`zh-TW`; item names and paths are never translated.
|
|
309
340
|
|
|
@@ -345,7 +376,7 @@ tool does not know are kept):
|
|
|
345
376
|
| `harness.<name>` | `true` / `false`, for `claude` `codex` `grok` `opencode` `openclaw` `copilot` `vibe` `devin` `agy` | `true` | |
|
|
346
377
|
| `picker_sort` | `name` / `cost` | `name` | |
|
|
347
378
|
| `picker_harness` | `all` or a harness name | `all` | |
|
|
348
|
-
| `picker_type` | `all
|
|
379
|
+
| `picker_type` | `all`, a type, or `mod` | `all` | |
|
|
349
380
|
| `telegram_chat_id` | a number, `-100...` or `@channel` | unset | `TG_CHAT_ID` |
|
|
350
381
|
|
|
351
382
|
The bot token is not in this file: it is in the OS keystore through telegram-kit
|
|
@@ -400,7 +431,7 @@ translated when `language` is `zh-TW`.
|
|
|
400
431
|
|
|
401
432
|
| harness | home | skill | agent | command | rule | plugin | mcp |
|
|
402
433
|
|---|---|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
403
|
-
| claude | `~/.claude` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ `~/.claude.json` |
|
|
434
|
+
| claude | `~/.claude` | ✓ | ✓ | ✓ | ✓ | ✓ (mods too) | ✓ `~/.claude.json` |
|
|
404
435
|
| codex | `~/.codex` | ✓ | ✓ | ✓ (`commands/` + `prompts/`) | — | — | ✓ `config.toml` |
|
|
405
436
|
| grok | `~/.grok` | ✓ | — | — | — | — | ✓ `config.toml` |
|
|
406
437
|
| opencode | `$XDG_CONFIG_HOME/opencode`, else `~/.config/opencode` | ✓ | — | ✓ (`command/`) | — | — | ✓ `opencode.json` (flag, *assumed*) |
|
|
@@ -810,8 +841,9 @@ Both sides are resolved first.
|
|
|
810
841
|
## Design and roadmap
|
|
811
842
|
|
|
812
843
|
Architecture, harness survey, cost model, known gaps and the phased roadmap
|
|
813
|
-
toward a public multi-OS release live in `docs/DESIGN.md`.
|
|
814
|
-
|
|
844
|
+
toward a public multi-OS release live in `docs/DESIGN.md`. The design for the
|
|
845
|
+
Claude Code pane mod (the picker inside Claude, driven by this CLI) is in
|
|
846
|
+
`docs/MOD.md`. Release notes are in `CHANGELOG.md`.
|
|
815
847
|
|
|
816
848
|
## Tests
|
|
817
849
|
|
|
@@ -67,6 +67,7 @@ from .backends.plugin_cli import claude_bin
|
|
|
67
67
|
from .harnesses import TYPES, harness_of, harnesses, project_view
|
|
68
68
|
from .mechanisms import (
|
|
69
69
|
dir_view,
|
|
70
|
+
fail_row,
|
|
70
71
|
legacy_parks,
|
|
71
72
|
migrate_parks,
|
|
72
73
|
settle,
|
|
@@ -285,7 +286,7 @@ def cmd_cost(state: dict, out: Result, harness: str | None = None,
|
|
|
285
286
|
+ (tail if i.enabled else out.paint(tail, "dim")))
|
|
286
287
|
out.row(i.harness, i.type, i.name, "cost", "ok", i.basis, show=False,
|
|
287
288
|
enabled=i.enabled, tokens=i.tokens, would_save=i.would_save,
|
|
288
|
-
chars=i.chars, shared_with=list(i.shared_with))
|
|
289
|
+
chars=i.chars, shared_with=list(i.shared_with), mod=i.mod)
|
|
289
290
|
live = sum(i.tokens for i in items)
|
|
290
291
|
saved = sum(i.would_save for i in items)
|
|
291
292
|
out.say(f"\n{len(items)} item(s): ~{live} tok loaded at startup; "
|
|
@@ -337,21 +338,39 @@ def cmd_toggle(args: argparse.Namespace, out: Result) -> None:
|
|
|
337
338
|
if harness != "claude":
|
|
338
339
|
die(f"--project supports only the claude layout, not {harness}", 4)
|
|
339
340
|
h = project_view(args.project)
|
|
340
|
-
if type_ not in h.types:
|
|
341
|
+
if type_ not in (*h.types, "all"):
|
|
341
342
|
die(f"--project supports {', '.join(h.types)} (not {type_})", 4)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
343
|
+
project = str(h.project)
|
|
344
|
+
else:
|
|
345
|
+
h, project = harness_of(harness), None
|
|
346
|
+
home, supported = h.home, h.types
|
|
347
|
+
if not home.is_dir():
|
|
348
|
+
die(f"{harness} is not installed ({home} does not exist)", 4)
|
|
349
|
+
if type_ not in (*supported, "all"):
|
|
350
|
+
have = f"it has: {', '.join(supported)}" if supported else "no supported types"
|
|
351
|
+
die(f"{harness} has no {type_} support ({have})", 4)
|
|
352
|
+
pairs = [(type_, n) for n in names]
|
|
353
|
+
if type_ == "all":
|
|
354
|
+
pairs = _every_type(action, names, harness, h.project)
|
|
355
|
+
for name in sorted(set(names) - {n for _, n in pairs}):
|
|
356
|
+
fail_row(out, args.dry_run, harness, "all", action, name,
|
|
357
|
+
f"no {'live' if action == 'disable' else 'disabled'} item named {name} "
|
|
358
|
+
f"on {harness}", batch=store.BATCH, project=project)
|
|
359
|
+
ops.apply_plan([ops.Op(harness, t, action, n, project) for t, n in pairs], out,
|
|
360
|
+
args.dry_run, batch=store.BATCH)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _every_type(action: str, names: list[str], harness: str,
|
|
364
|
+
project: Path | None) -> list[tuple[str, str]]:
|
|
365
|
+
"""`all`: each (type, name) on `harness` that `action` can act on -- live items for
|
|
366
|
+
disable, parked ones for enable -- in one scope (user, or the --project dir)."""
|
|
367
|
+
state, table = profiles.scope(load_state(write_back=False), project)
|
|
368
|
+
want = action == "disable"
|
|
369
|
+
# plugin ids are `name@marketplace`: skip the claude CLI call unless one could match
|
|
370
|
+
plugins = project is None and any("@" in n for n in names)
|
|
371
|
+
return sorted({(i.type, i.name) for i in cost.inventory(state, table, plugins=plugins)
|
|
372
|
+
if i.name in names and i.enabled == want
|
|
373
|
+
and harness in (i.harness, *i.shared_with)})
|
|
355
374
|
|
|
356
375
|
|
|
357
376
|
def cmd_migrate(out: Result) -> None:
|
|
@@ -690,7 +709,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
690
709
|
up2.add_argument("--dry-run", action="store_true", help="show the plan; change nothing")
|
|
691
710
|
for name, verb in (("disable", "park"), ("enable", "restore")):
|
|
692
711
|
sp = sub.add_parser(name, parents=[common], help=f"{verb} one or more items")
|
|
693
|
-
sp.add_argument("type", choices=TYPES, nargs="?" if name == "enable" else None)
|
|
712
|
+
sp.add_argument("type", choices=(*TYPES, "all"), nargs="?" if name == "enable" else None)
|
|
694
713
|
sp.add_argument("names", nargs="*" if name == "enable" else "+", metavar="name")
|
|
695
714
|
sp.add_argument("--dry-run", action="store_true",
|
|
696
715
|
help="show the plan; change nothing")
|
|
@@ -71,6 +71,7 @@ class Item:
|
|
|
71
71
|
basis: str
|
|
72
72
|
shared_with: tuple[str, ...] = ()
|
|
73
73
|
description: str = "" # frontmatter description, when the file has one
|
|
74
|
+
mod: bool = False # a plugin that loads UI modules (see is_mod)
|
|
74
75
|
|
|
75
76
|
|
|
76
77
|
def tokens_of(chars: int) -> int:
|
|
@@ -206,6 +207,26 @@ def backup_tools(entry: dict) -> int | None:
|
|
|
206
207
|
return _entry_tools(raw)
|
|
207
208
|
|
|
208
209
|
|
|
210
|
+
def is_mod(root: Path) -> bool:
|
|
211
|
+
"""A mod is a plugin whose hooks/hooks.json has a top-level `modules` key: it loads
|
|
212
|
+
.mjs/.tsx code that draws UI; plain `hooks` (shell commands) do not make one."""
|
|
213
|
+
try:
|
|
214
|
+
data = json.loads((root / "hooks" / "hooks.json").read_text(encoding="utf-8"))
|
|
215
|
+
except (OSError, ValueError):
|
|
216
|
+
return False
|
|
217
|
+
return isinstance(data, dict) and "modules" in data
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def plugin_is_mod(cache: Path | None, pid: str, root: object = None) -> bool:
|
|
221
|
+
"""`pid` (`name@marketplace`) is a mod at its reported install path, or in any cached
|
|
222
|
+
version under `cache/<marketplace>/<name>/`: the CLI's path can differ, and a parked
|
|
223
|
+
plugin has none."""
|
|
224
|
+
if isinstance(root, str) and is_mod(Path(root)):
|
|
225
|
+
return True
|
|
226
|
+
name, _, market = pid.partition("@")
|
|
227
|
+
return bool(cache and name and market) and any(is_mod(d) for d in (cache / market / name).glob("*"))
|
|
228
|
+
|
|
229
|
+
|
|
209
230
|
def plugin_estimate(root: Path) -> Estimate:
|
|
210
231
|
"""A plugin bundle: sum of what it ships, read from its install path."""
|
|
211
232
|
parts = [("skill", d.name, d / "SKILL.md") for d in sorted((root / "skills").glob("*"))
|
|
@@ -253,13 +274,14 @@ def inventory(state: dict, table: dict, warn: Callable[[str], None] = lambda m:
|
|
|
253
274
|
if plugins and claude and claude.home.is_dir() and "plugin" in claude.types
|
|
254
275
|
else None)
|
|
255
276
|
deferred: list[tuple[Item, Callable[[], Estimate]]] = [] # file reads, run in parallel below
|
|
277
|
+
cache = claude.home / "plugins" / "cache" if claude else None
|
|
256
278
|
|
|
257
|
-
def add(h: str, t: str, n: str, enabled: bool, est, shared=()) -> None:
|
|
279
|
+
def add(h: str, t: str, n: str, enabled: bool, est, shared=(), mod: bool = False) -> None:
|
|
258
280
|
"""`est` is an Estimate, or a no-arg callable returning one (read later, in a pool)."""
|
|
259
281
|
enabled = enabled and (h, t, n) not in flagged
|
|
260
282
|
if (h, t, n) not in seen:
|
|
261
283
|
seen.add((h, t, n))
|
|
262
|
-
item = Item(h, t, n, enabled, 0, 0, None, "", tuple(shared))
|
|
284
|
+
item = Item(h, t, n, enabled, 0, 0, None, "", tuple(shared), mod=mod)
|
|
263
285
|
items.append(item)
|
|
264
286
|
if callable(est):
|
|
265
287
|
deferred.append((item, est))
|
|
@@ -300,7 +322,8 @@ def inventory(state: dict, table: dict, warn: Callable[[str], None] = lambda m:
|
|
|
300
322
|
root = p.get("installPath")
|
|
301
323
|
add("claude", "plugin", p["id"], p.get("enabled", True) is not False,
|
|
302
324
|
(lambda r=root: plugin_estimate(Path(r))) if isinstance(root, str)
|
|
303
|
-
else (0, None, "no installPath")
|
|
325
|
+
else (0, None, "no installPath"),
|
|
326
|
+
mod=plugin_is_mod(cache, p["id"], root))
|
|
304
327
|
|
|
305
328
|
for e in state.get("disabled", {}).values():
|
|
306
329
|
h, t, n = e.get("harness", "claude"), e.get("type"), e.get("name")
|
|
@@ -319,7 +342,8 @@ def inventory(state: dict, table: dict, warn: Callable[[str], None] = lambda m:
|
|
|
319
342
|
return file_estimate(t, n, Path(p) if isinstance(p, str) and p else None)
|
|
320
343
|
shared = e.get("shared_with")
|
|
321
344
|
add(h, t, n, False, est,
|
|
322
|
-
[x for x in shared if isinstance(x, str)] if isinstance(shared, list) else ()
|
|
345
|
+
[x for x in shared if isinstance(x, str)] if isinstance(shared, list) else (),
|
|
346
|
+
mod=t == "plugin" and h == "claude" and plugin_is_mod(cache, n))
|
|
323
347
|
with pool:
|
|
324
348
|
for (item, _), est in zip(deferred, pool.map(lambda d: d[1](), deferred)):
|
|
325
349
|
_fill(item, est)
|
|
@@ -37,7 +37,8 @@ def _dry_run() -> tuple[str, str]:
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _type_arg() -> tuple[str, str]:
|
|
40
|
-
return ("<type>", " | ".join(TYPES)
|
|
40
|
+
return ("<type>", " | ".join(TYPES) + " | all "
|
|
41
|
+
+ t("help.arg.all", "(every type with that name)"))
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
def commands(default_harness: str = "claude") -> dict[str, Cmd]:
|
|
@@ -57,7 +58,8 @@ def commands(default_harness: str = "claude") -> dict[str, Cmd]:
|
|
|
57
58
|
"Without --harness this acts on %s.", default_harness),
|
|
58
59
|
args=[_type_arg(), ("<name>...", t("help.arg.names", "one or more item names"))],
|
|
59
60
|
options=[_dry_run(), _project()],
|
|
60
|
-
examples=["disable skill my-skill", "disable
|
|
61
|
+
examples=["disable skill my-skill", "disable all my-tool",
|
|
62
|
+
"disable mcp my-server --harness codex",
|
|
61
63
|
"disable agent my-agent --project . --dry-run"]),
|
|
62
64
|
"enable": Cmd(
|
|
63
65
|
"enable <type> <name>... | enable --all [--dry-run] [--project dir]",
|
|
@@ -13,6 +13,7 @@ CATALOG: dict[str, str] = {
|
|
|
13
13
|
"help.disable.summary": "停用(暫存)一或多個項目",
|
|
14
14
|
"help.disable.desc": "把項目移出 harness 的視野,不會刪除任何東西。未指定 --harness 時作用於 %s。",
|
|
15
15
|
"help.enable.summary": "還原一或多個項目",
|
|
16
|
+
"help.arg.all": "(同名的所有類型)",
|
|
16
17
|
"help.enable.desc": "把暫存的項目放回去。未指定 --harness 時作用於 %s;"
|
|
17
18
|
"--all 還原全部(可搭配一個 --harness)。",
|
|
18
19
|
"help.undo.summary": "復原最近一次記錄的批次",
|
|
@@ -12,6 +12,7 @@ CATALOG: dict[str, str] = {
|
|
|
12
12
|
"picker.type.command": "指令",
|
|
13
13
|
"picker.type.rule": "規則",
|
|
14
14
|
"picker.type.plugin": "外掛",
|
|
15
|
+
"picker.type.mod": "Mods",
|
|
15
16
|
"picker.type.mcp": "MCP",
|
|
16
17
|
"picker.sort.name": "名稱",
|
|
17
18
|
"picker.sort.cost": "成本",
|
|
@@ -73,6 +74,7 @@ CATALOG: dict[str, str] = {
|
|
|
73
74
|
"picker.help.sort": "循環切換排序:名稱 / 成本(由大到小)",
|
|
74
75
|
"picker.help.profile": "設定檔:暫存一個,或儲存目前啟用的狀態",
|
|
75
76
|
"picker.help.all": "切換所有可見的列",
|
|
77
|
+
"picker.help.same_name": "切換此 harness 上同名的所有類型",
|
|
76
78
|
"picker.help.help": "顯示此說明",
|
|
77
79
|
"picker.help.cost": "成本 = 工作階段開始時載入的約略 token 數(字元數/4)。",
|
|
78
80
|
"picker.help.close": "按任意鍵關閉說明",
|
|
@@ -41,7 +41,7 @@ DEFAULTS: dict[str, Spec] = {
|
|
|
41
41
|
**{f"harness.{n}": Spec("bool", True) for n in HARNESS_NAMES},
|
|
42
42
|
"picker_sort": Spec("choice", "name", ("name", "cost")),
|
|
43
43
|
"picker_harness": Spec("choice", "all", ("all", *HARNESS_NAMES)),
|
|
44
|
-
"picker_type": Spec("choice", "all", ("all", *harnesses.TYPES)),
|
|
44
|
+
"picker_type": Spec("choice", "all", ("all", *harnesses.TYPES, "mod")),
|
|
45
45
|
"language": Spec("choice", "en", ("en", "zh-TW"), "AGENT_TOGGLE_LANG"),
|
|
46
46
|
"telegram_chat_id": Spec("chat_id"),
|
|
47
47
|
}
|
|
@@ -17,12 +17,15 @@ python3 __AGENT_TOGGLE_ROOT__/agent_toggle.py <command> --json # if agent-togg
|
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
Commands: `status` (run first), `doctor` (read-only health check), `list [type]`,
|
|
20
|
-
`cost [--type T]` (startup token estimates, biggest first), `disable <type> <name
|
|
20
|
+
`cost [--type T]` (startup token estimates, biggest first), `disable <type> <name>...` (type `all`: every type with that name),
|
|
21
21
|
`enable <type> <name>...`, `enable --all`, `undo` (reverse the last batch),
|
|
22
22
|
`profile save|apply|diff <name>`, `profile list`, `migrate`; add `--harness claude|codex|grok|opencode|openclaw|copilot|vibe|devin|agy`
|
|
23
23
|
(default `claude`) and `--dry-run` to preview. `--project <dir>` targets a repo's own `.claude/` and `.mcp.json`
|
|
24
24
|
(warn the user: git shows the deletion). `<type>` = `skill` / `agent` /
|
|
25
25
|
`command` / `rule` / `plugin` / `mcp`. A colon addresses nesting: `orch:batch`.
|
|
26
|
+
A mod (a plugin whose `hooks/hooks.json` has a `modules` key) is type `plugin`;
|
|
27
|
+
`cost --type plugin --json` marks each one `"mod": true`, so "turn off the mods" means
|
|
28
|
+
disabling those plugin ids.
|
|
26
29
|
A row with `shared_with` means another harness reads the same directory (OpenCode
|
|
27
30
|
aliasing a skills dir): the change affects them too, so say so.
|
|
28
31
|
`ui` is an interactive picker for the user, not for you.
|
|
@@ -21,7 +21,7 @@ You are running in `__HARNESS__`: pass `--harness __HARNESS__` for its own items
|
|
|
21
21
|
|
|
22
22
|
Commands: `status` (run first; shows which types this harness supports),
|
|
23
23
|
`doctor` (read-only health check), `list [type]`, `cost [--type T]` (startup
|
|
24
|
-
token estimates, biggest first), `disable <type> <name
|
|
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 <name>`,
|
|
26
26
|
`profile list`; add `--dry-run` to preview. `<type>` = `skill` / `agent` /
|
|
27
27
|
`command` / `rule` / `plugin` / `mcp`. A colon addresses nesting: `orch:batch`.
|
|
@@ -17,6 +17,7 @@ from .model import (
|
|
|
17
17
|
prefs,
|
|
18
18
|
profile_command,
|
|
19
19
|
profile_listing,
|
|
20
|
+
toggle_same_name,
|
|
20
21
|
type_label,
|
|
21
22
|
visible,
|
|
22
23
|
)
|
|
@@ -106,9 +107,12 @@ def loop(rows: list[Row], stdin, stdout, project: Path | None = None,
|
|
|
106
107
|
f"{profile_listing()}\np <number|name> | p save <name>")
|
|
107
108
|
+ "\n")
|
|
108
109
|
redraw = bool(line)
|
|
110
|
+
elif low.startswith("n ") and (idx := _numbers(cmd[2:], len(shown))) is not None:
|
|
111
|
+
for i in idx: # every type with that row's name
|
|
112
|
+
toggle_same_name(kept, shown[i])
|
|
109
113
|
elif low in ("?", "help"):
|
|
110
114
|
stdout.write("Numbers tick/untick rows (unticked = parked); nothing is applied "
|
|
111
|
-
"until `a`. `p` lists profiles; `p <number|name>` stages one, "
|
|
115
|
+
"until `a`. `n <number>` toggles every type with that row's name. `p` lists profiles; `p <number|name>` stages one, "
|
|
112
116
|
"`p save <name>` saves the live state.\n")
|
|
113
117
|
redraw = False
|
|
114
118
|
elif cmd and (idx := _numbers(cmd, len(shown))) is not None:
|