agent-toggle 0.7.0__tar.gz → 0.7.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.7.0 → agent_toggle-0.7.2}/PKG-INFO +1 -1
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/__init__.py +1 -1
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/fs.py +15 -1
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/mechanisms.py +25 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle.egg-info/PKG-INFO +1 -1
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_fs.py +17 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_store.py +14 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/LICENSE +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/README.md +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/__main__.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/backends/__init__.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/backends/flag_json.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/backends/mcp_json.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/backends/mcp_toml.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/backends/plugin_cli.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/cli.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/companions.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/config.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/cost.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/doctor.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/harnesses.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/help.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/i18n.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/locales/__init__.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/locales/zh_tw_cli.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/locales/zh_tw_common.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/locales/zh_tw_config.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/locales/zh_tw_picker.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ops.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/output.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/profiles.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/settings.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/shims/claude.md.tmpl +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/shims/generic.md.tmpl +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/spinner.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/store.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/toml_check.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ui/__init__.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ui/config_menu.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ui/menu.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ui/model.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ui/picker.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/ui/theme.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/undo.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/update_check.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle/update_prompt.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle.egg-info/SOURCES.txt +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle.egg-info/dependency_links.txt +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle.egg-info/entry_points.txt +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle.egg-info/requires.txt +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/agent_toggle.egg-info/top_level.txt +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/pyproject.toml +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/setup.cfg +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_alias.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_backends.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_cli.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_cli_surface.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_color.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_companions.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_completions.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_config.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_config_menu.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_conformance.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_containment.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_cost.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_crash.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_doctor.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_encoding.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_harnesses.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_help.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_i18n.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_install.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_mechanisms.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_menu.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_picker.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_platform.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_profiles.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_project.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_safety.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_secret_audit.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_settings.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_theme.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_undo.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/tests/test_update_check.py +0 -0
- {agent_toggle-0.7.0 → agent_toggle-0.7.2}/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.7.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -416,6 +416,9 @@ def safe_move(src: Path, dest_dir: Path) -> Path:
|
|
|
416
416
|
if target.exists() or target.is_symlink():
|
|
417
417
|
raise FileExistsError(f"{target} already exists -- refusing to overwrite")
|
|
418
418
|
|
|
419
|
+
if src.is_symlink() and link_text(src, dest_dir) != os.readlink(src):
|
|
420
|
+
_copy_move(src, target) # a rename keeps the relative text, now dangling
|
|
421
|
+
return target
|
|
419
422
|
try:
|
|
420
423
|
os.rename(src, target) # same filesystem: one atomic step
|
|
421
424
|
except OSError as e:
|
|
@@ -425,6 +428,17 @@ def safe_move(src: Path, dest_dir: Path) -> Path:
|
|
|
425
428
|
return target
|
|
426
429
|
|
|
427
430
|
|
|
431
|
+
def link_text(src: Path, dest_dir: Path) -> str:
|
|
432
|
+
"""The symlink `src`'s text as seen from `dest_dir`: a relative link keeps its target,
|
|
433
|
+
not its text, since parks sit at another depth (skills/ vs parked/user/<h>/skills/)."""
|
|
434
|
+
link = os.readlink(src)
|
|
435
|
+
if os.path.isabs(link):
|
|
436
|
+
return link
|
|
437
|
+
# the kernel resolves a relative link from the link's physical dir
|
|
438
|
+
target = os.path.normpath(os.path.join(os.path.realpath(src.parent), link))
|
|
439
|
+
return os.path.relpath(target, os.path.realpath(dest_dir))
|
|
440
|
+
|
|
441
|
+
|
|
428
442
|
def move_leftovers(src: Path, target: Path) -> tuple[Path, Path]:
|
|
429
443
|
"""(half copy, trash) a killed cross-fs move can leave beside `target` / `src`."""
|
|
430
444
|
return (target.with_name(f".{target.name}.agent-toggle-tmp"),
|
|
@@ -477,7 +491,7 @@ def _copy_move(src: Path, target: Path) -> None:
|
|
|
477
491
|
remove_leftover(tmp) # a killed earlier attempt's half copy
|
|
478
492
|
try:
|
|
479
493
|
if src.is_symlink():
|
|
480
|
-
os.symlink(
|
|
494
|
+
os.symlink(link_text(src, target.parent), tmp)
|
|
481
495
|
elif src.is_dir():
|
|
482
496
|
shutil.copytree(src, tmp, symlinks=True)
|
|
483
497
|
else:
|
|
@@ -360,6 +360,31 @@ def migrate_parks(state: dict, table: dict | None = None) -> list[str]:
|
|
|
360
360
|
lines.append(f"moved {len(moved)} item(s) {old} -> {v.parked}"
|
|
361
361
|
+ (f", repointed {repointed} state entry(ies)" if repointed else "")
|
|
362
362
|
+ ("" if _exists(old) else f", removed {old}"))
|
|
363
|
+
return lines + repair_parked_links(table)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def repair_parked_links(table: dict) -> list[str]:
|
|
367
|
+
"""0.7.0's migrate moved relative symlinks with their text unchanged, so they dangle
|
|
368
|
+
from the deeper park dir; re-aim each one whose text still resolves from its live dir."""
|
|
369
|
+
lines, seen = [], set()
|
|
370
|
+
for hn, h in table.items():
|
|
371
|
+
for type_, subs in h.dirs.items():
|
|
372
|
+
for sub in subs:
|
|
373
|
+
v = dir_view(table, hn, type_, h.home, sub)
|
|
374
|
+
if v.parked in seen or not v.parked.is_dir() or v.parked.is_symlink():
|
|
375
|
+
continue
|
|
376
|
+
seen.add(v.parked)
|
|
377
|
+
for p in sorted(v.parked.iterdir()):
|
|
378
|
+
if not p.is_symlink() or p.exists() or os.path.isabs(link := os.readlink(p)):
|
|
379
|
+
continue
|
|
380
|
+
target = os.path.normpath(os.path.join(os.path.realpath(v.live), link))
|
|
381
|
+
if not os.path.exists(target):
|
|
382
|
+
continue # dangles from its live dir too: not ours
|
|
383
|
+
tmp = fs.move_leftovers(p, p)[0]
|
|
384
|
+
fs.remove_leftover(tmp)
|
|
385
|
+
os.symlink(os.path.relpath(target, os.path.realpath(v.parked)), tmp)
|
|
386
|
+
fs.replace(tmp, p) # atomic: the link is never missing
|
|
387
|
+
lines.append(f"re-aimed dangling link {p} -> {target}")
|
|
363
388
|
return lines
|
|
364
389
|
|
|
365
390
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-toggle
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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
|
|
@@ -38,6 +38,23 @@ class SafeMoveTest(SandboxCase):
|
|
|
38
38
|
# existing parked copy untouched
|
|
39
39
|
self.assertEqual((self.home / "skills-disabled/a/SKILL.md").read_text(encoding="utf-8"), "old")
|
|
40
40
|
|
|
41
|
+
@unittest.skipUnless(CAN_SYMLINK, "cannot create symlinks here")
|
|
42
|
+
def test_relative_link_keeps_its_target_across_depths(self) -> None:
|
|
43
|
+
real = self.write(".repos/r/demo/SKILL.md", "hi").parent
|
|
44
|
+
link = self.home / "skills" / "demo"
|
|
45
|
+
link.parent.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
link.symlink_to("../.repos/r/demo")
|
|
47
|
+
parked = fs.safe_move(link, fs.user_park("claude", "skills"))
|
|
48
|
+
self.assertEqual(parked.resolve(), real.resolve()) # not dangling while parked
|
|
49
|
+
back = fs.safe_move(parked, self.home / "skills")
|
|
50
|
+
# the round trip is exact (Windows re-aims with native separators)
|
|
51
|
+
self.assertEqual(os.path.normpath(os.readlink(back)), os.path.normpath("../.repos/r/demo"))
|
|
52
|
+
absolute = self.home / "skills" / "abs"
|
|
53
|
+
absolute.symlink_to(real)
|
|
54
|
+
text = os.readlink(absolute) # Windows may add \\?\
|
|
55
|
+
self.assertEqual(os.readlink(fs.safe_move(absolute, fs.user_park("claude", "skills"))),
|
|
56
|
+
text) # absolute: untouched
|
|
57
|
+
|
|
41
58
|
|
|
42
59
|
POSIX = os.name != "nt"
|
|
43
60
|
|
|
@@ -152,6 +152,20 @@ class MigrateParksTest(CliCase):
|
|
|
152
152
|
self.assertEqual(store.load_state()["disabled"]["claude:skill:demo-skill"]["parked_at"],
|
|
153
153
|
str(self.user_parked("skills", "demo-skill")))
|
|
154
154
|
|
|
155
|
+
@unittest.skipUnless(CAN_SYMLINK, "cannot create symlinks here")
|
|
156
|
+
def test_a_link_left_dangling_by_0_7_0_is_re_aimed(self) -> None:
|
|
157
|
+
real = self.write(".repos/r/linked/SKILL.md", "hi").parent
|
|
158
|
+
link = self.user_parked("skills", "linked")
|
|
159
|
+
link.parent.mkdir(parents=True, exist_ok=True)
|
|
160
|
+
link.symlink_to("../.repos/r/linked") # 0.7.0 kept the text
|
|
161
|
+
gone = self.user_parked("skills", "gone")
|
|
162
|
+
gone.symlink_to("../.repos/nowhere") # dangles from skills/ too
|
|
163
|
+
self.run_json("migrate")
|
|
164
|
+
self.assertEqual(link.resolve(), real.resolve())
|
|
165
|
+
self.assertEqual(os.readlink(gone), "../.repos/nowhere") # left alone
|
|
166
|
+
rc, env = self.run_json("migrate")
|
|
167
|
+
self.assertNotIn("re-aimed", env["results"][0]["detail"]) # idempotent
|
|
168
|
+
|
|
155
169
|
|
|
156
170
|
class LogSchemaTest(SandboxCase):
|
|
157
171
|
FIELDS = {"ts", "harness", "type", "name", "action", "result", "batch",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|