Repository navigation
Expand file tree
/
Copy pathcode_plugin.py
More file actions
160 lines (148 loc) · 5.96 KB
/
Copy pathcode_plugin.py
File metadata and controls
160 lines (148 loc) · 5.96 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
"""Install only native Claude Code guidance; the global entry owns MCP transport."""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import shutil
import subprocess
from .storage import WorkspaceError, atomic_json, home
PLUGIN_ID = "sqlrooms@sqlrooms-local"
def command(*args):
executable = shutil.which("claude")
if not executable:
raise WorkspaceError(
"client_missing",
"Claude Code is not installed. Install it explicitly, then rerun setup; no software was installed.",
)
try:
result = subprocess.run(
[executable, "plugin", *args], capture_output=True, text=True, timeout=30
)
result.check_returncode()
except (OSError, subprocess.SubprocessError):
raise WorkspaceError(
"plugin_setup_failed",
"Claude Code could not complete native plugin setup. Inspect `claude plugin list` and retry setup; the MCP configuration is retained.",
) from None
return result.stdout
def prepare_files(source: Path):
root = home() / "integrations/claude-code"
manifest_path = root / "managed-files.json"
previous = json.loads(manifest_path.read_text()) if manifest_path.exists() else {}
files = {
".claude-plugin/marketplace.json": json.dumps(
{
"name": "sqlrooms-local",
"owner": {"name": "SQLRooms"},
"plugins": [{"name": "sqlrooms", "source": "./plugin"}],
}
).encode(),
"plugin/.claude-plugin/plugin.json": (
source / ".claude-plugin/plugin.json"
).read_bytes(),
}
for path in (source / "skills").rglob("*"):
if path.is_file():
files["plugin/" + str(path.relative_to(source))] = path.read_bytes()
for name, content in files.items():
target = root / name
if (
target.exists()
and target.read_bytes() != content
and hashlib.sha256(target.read_bytes()).hexdigest() != previous.get(name)
):
raise WorkspaceError(
"guidance_conflict", f"Customized guidance preserved at {target}."
)
for name, content in files.items():
target = root / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
# Files are guidance, not credentials. Root metadata is owner-only.
root.chmod(0o700)
atomic_json(
manifest_path,
{name: hashlib.sha256(content).hexdigest() for name, content in files.items()},
)
return root
def install(source: Path):
if (
Path(
os.environ.get("CLAUDE_CONFIG_DIR") or str(Path.home() / ".claude")
).expanduser()
/ "skills/sqlrooms"
).exists():
raise WorkspaceError(
"guidance_conflict",
"A loose SQLRooms skill already exists. Remove or move it explicitly before installing the native plugin.",
)
installed = json.loads(command("list", "--json"))
entries = installed if isinstance(installed, list) else installed.get("plugins", [])
for entry in entries:
identifier = entry.get("id", entry.get("name", ""))
if identifier.startswith("sqlrooms@") and identifier != PLUGIN_ID:
raise WorkspaceError(
"guidance_conflict",
"Another SQLRooms native plugin is installed. Keep one guidance installation per host.",
)
root = home() / "integrations/claude-code"
ownership_path = home() / "setup-claude-code.json"
ownership = (
json.loads(ownership_path.read_text()) if ownership_path.exists() else {}
)
already_installed = any(
entry.get("id", entry.get("name")) == PLUGIN_ID for entry in entries
)
marketplaces = json.loads(command("marketplace", "list", "--json"))
existing_market = next(
(m for m in marketplaces if m.get("name") == "sqlrooms-local"), None
)
owned = ownership.get("nativePlugin") == PLUGIN_ID
if (already_installed and not owned) or (
existing_market
and (
not owned
or existing_market.get("source") != "directory"
or Path(existing_market.get("path", "")).resolve() != root.resolve()
)
):
raise WorkspaceError(
"guidance_conflict",
"An existing SQLRooms plugin or marketplace is not owned by this setup. It has been preserved.",
)
root = prepare_files(source)
command("marketplace", "add", str(root))
# Record ownership immediately after creating the marketplace, so an
# interrupted installation can be repaired without adopting foreign files.
atomic_json(ownership_path, {**ownership, "nativePlugin": PLUGIN_ID})
if already_installed:
command("marketplace", "update", "sqlrooms-local")
command("update", PLUGIN_ID)
else:
command("install", PLUGIN_ID, "--scope", "user")
return PLUGIN_ID
def uninstall():
marketplaces = json.loads(command("marketplace", "list", "--json"))
existing = next(
(m for m in marketplaces if m.get("name") == "sqlrooms-local"), None
)
root = home() / "integrations/claude-code"
if existing and (
existing.get("source") != "directory"
or Path(existing.get("path", "")).resolve() != root.resolve()
):
raise WorkspaceError(
"guidance_conflict", "Customized SQLRooms marketplace and plugin preserved."
)
installed = json.loads(command("list", "--json"))
entries = installed if isinstance(installed, list) else installed.get("plugins", [])
if any(entry.get("id", entry.get("name")) == PLUGIN_ID for entry in entries):
if not existing:
raise WorkspaceError(
"guidance_conflict",
"Plugin source cannot be verified; existing plugin preserved.",
)
command("uninstall", PLUGIN_ID, "--scope", "user")
if existing:
command("marketplace", "remove", "sqlrooms-local")