Repository navigation
Expand file tree
/
Copy pathuninstall
More file actions
executable file
·144 lines (126 loc) · 5.87 KB
/
Copy pathuninstall
File metadata and controls
executable file
·144 lines (126 loc) · 5.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#!/usr/bin/env python3
"""Remove what install installed. Leaves source intact — AND user data intact.
- Removes ~/.myteams/ (pipeline + installed teams), PRESERVING the user data
that lives beside them: authored acceptance contracts (~/.myteams/contracts/)
and the LLM/MCP config (~/.myteams/config.json). Both are user-authored
state install never wrote; pass --purge to delete them too.
- Removes the ~/.local/bin/myteams symlink (and any stale ~/.local/bin/myteam)
- Removes the ~/.claude/skills/myagenticteams/ plugin (and the retired
~/.claude/skills/myteams/ umbrella skill, plus any stale myteam skill)
references/ bodies are committed source (vendored at author time via
reference_vendor, not generated at install) — uninstall does not touch them.
"""
from __future__ import annotations
import json
import shutil
import sys
from pathlib import Path
MYTEAMS_HOME = Path.home() / ".myteams"
BIN_DIR = Path.home() / ".local" / "bin"
SKILLS_DEST = Path.home() / ".claude" / "skills"
CLAUDE_SETTINGS = Path.home() / ".claude" / "settings.json"
# The CLI binary name (the ~/.local/bin/<CLI_NAME> symlink); also the retired
# umbrella SKILL dir name, removed here for a clean break.
CLI_NAME = "myteams"
# The skills-directory plugin install assembles under ~/.claude/skills/.
PLUGIN_NAME = "myagenticteams"
# The old tool's command name. uninstall removes any stale ~/.local/bin/myteam
# alongside the myteams symlink so neither name survives. Only the symlink is
# touched — never the on-disk ~/.myteam data dir.
STALE_SKILL_NAME = "myteam"
# Mirrors install's BUILD_GUARD_MARKER — the marker line install stamps into the
# opt-in PreToolUse hook command, used here to find and remove exactly that hook.
BUILD_GUARD_MARKER = "# myteams-build-guard"
# The OLD tool's marker — removed here too so an upgrade-then-uninstall leaves no
# stale `myteam` hook pointing at a removed pipeline.
LEGACY_BUILD_GUARD_MARKER = "# myteam-build-guard"
# User-authored state living under ~/.myteams that install never wrote and
# uninstall must not destroy (previously the whole tree was rm -rf'd, taking
# authored acceptance contracts and the LLM/MCP config with it).
USER_DATA = ("contracts", "config.json")
def remove_home(purge: bool = False) -> None:
if not MYTEAMS_HOME.exists():
print(f" (nothing installed at {MYTEAMS_HOME})")
return
if purge:
shutil.rmtree(MYTEAMS_HOME)
print(f"\033[1;32m✓ removed {MYTEAMS_HOME} (purged, incl. user data)\033[0m")
return
preserved = []
for child in sorted(MYTEAMS_HOME.iterdir()):
if child.name in USER_DATA:
preserved.append(child.name)
continue
shutil.rmtree(child) if child.is_dir() else child.unlink()
if preserved:
print(f"\033[1;32m✓ removed installed content under {MYTEAMS_HOME}\033[0m")
print(f" preserved user data: {', '.join(preserved)} "
f"(pass --purge to delete)")
else:
shutil.rmtree(MYTEAMS_HOME)
print(f"\033[1;32m✓ removed {MYTEAMS_HOME}\033[0m")
def remove_symlink() -> None:
# Remove the myteams symlink and any stale myteam symlink left by the old
# tool (clean break — neither command name should survive). Only the symlinks
# are touched; the on-disk ~/.myteam data dir is left untouched.
for name in (CLI_NAME, STALE_SKILL_NAME):
link = BIN_DIR / name
if link.exists() or link.is_symlink():
link.unlink()
print(f"\033[1;32m✓ removed symlink {link}\033[0m")
else:
print(f" (symlink not found at {link})")
def remove_skill() -> None:
# Remove the myagenticteams plugin, the retired `myteams` umbrella skill, and
# any stale `myteam` skill dir left by the old tool (clean break — none should
# survive). Only the skill dirs under ~/.claude/skills/ are touched.
for name in (PLUGIN_NAME, CLI_NAME, STALE_SKILL_NAME):
dest = SKILLS_DEST / name
if dest.exists():
shutil.rmtree(dest)
print(f"\033[1;32m✓ removed skill {dest}\033[0m")
else:
print(f" (skill not installed at {dest})")
def remove_build_guard_hook(settings_path: Path = CLAUDE_SETTINGS) -> None:
"""Remove the opt-in build-guard PreToolUse hook that
`install --with-build-guard-hook` may have added — the inverse of install's
register_build_guard_hook, so uninstall never leaves a hook pointing at a
removed ~/.myteams. Idempotent; other hooks and a non-JSON settings file are
left untouched."""
if not settings_path.is_file():
return
try:
settings = json.loads(settings_path.read_text(encoding="utf-8"))
except json.JSONDecodeError:
print(f" ({settings_path} is not valid JSON — hook left untouched)")
return
pre = (settings.get("hooks") or {}).get("PreToolUse")
if not isinstance(pre, list):
return
markers = (BUILD_GUARD_MARKER, LEGACY_BUILD_GUARD_MARKER)
kept, removed = [], False
for entry in pre:
orig = entry.get("hooks", [])
hooks = [h for h in orig
if not any(m in (h.get("command") or "") for m in markers)]
if len(hooks) != len(orig):
removed = True
if hooks: # other hooks remain → keep, trimmed
kept.append({**entry, "hooks": hooks})
# else: the entry was solely ours → drop it
else:
kept.append(entry) # untouched entry
if not removed:
return
settings["hooks"]["PreToolUse"] = kept
settings_path.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
print(f"\033[1;32m✓ removed build-guard hook from {settings_path}\033[0m")
def main() -> int:
purge = "--purge" in sys.argv[1:]
remove_home(purge=purge)
remove_symlink()
remove_skill()
remove_build_guard_hook()
return 0
if __name__ == "__main__":
sys.exit(main())