Skip to content

Commit 3a7b921

Browse files
authored
refactor: deep clean plugins (#31)
* refactor: deep clean plugins * refactor: modernize plugin system with Python 3.12+ typing and simplified discovery - Update typing to Python 3.12+ style (Dict->dict, Optional->union types) - Simplify plugin discovery using PLUGIN_CLASS exports instead of dir() reflection - Add public get_user_ids() and set_ownership() functions in cubbi_init - Add create_directory_with_ownership() helper method to ToolPlugin base class - Replace initialize() + integrate_mcp_servers() pattern with unified configure() - Add is_already_configured() checks to prevent overwriting existing configs - Remove excessive comments and clean up code structure - All 5 plugins updated: goose, opencode, claudecode, aider, crush * fix: remove duplicate
1 parent a709071 commit 3a7b921

6 files changed

Lines changed: 175 additions & 403 deletions

File tree

cubbi/images/aider/aider_plugin.py

Lines changed: 21 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,15 @@
33
import os
44
import stat
55
from pathlib import Path
6-
from typing import Dict
76

8-
from cubbi_init import ToolPlugin, cubbi_config
7+
from cubbi_init import ToolPlugin, cubbi_config, set_ownership
98

109

1110
class AiderPlugin(ToolPlugin):
1211
@property
1312
def tool_name(self) -> str:
1413
return "aider"
1514

16-
def _get_user_ids(self) -> tuple[int, int]:
17-
return cubbi_config.user.uid, cubbi_config.user.gid
18-
19-
def _set_ownership(self, path: Path) -> None:
20-
user_id, group_id = self._get_user_ids()
21-
try:
22-
os.chown(path, user_id, group_id)
23-
except OSError as e:
24-
self.status.log(f"Failed to set ownership for {path}: {e}", "WARNING")
25-
2615
def _get_aider_config_dir(self) -> Path:
2716
return Path("/home/cubbi/.aider")
2817

@@ -33,28 +22,23 @@ def _ensure_aider_dirs(self) -> tuple[Path, Path]:
3322
config_dir = self._get_aider_config_dir()
3423
cache_dir = self._get_aider_cache_dir()
3524

36-
# Create directories
37-
for directory in [config_dir, cache_dir]:
38-
try:
39-
directory.mkdir(mode=0o755, parents=True, exist_ok=True)
40-
self._set_ownership(directory)
41-
except OSError as e:
42-
self.status.log(
43-
f"Failed to create Aider directory {directory}: {e}", "ERROR"
44-
)
25+
self.create_directory_with_ownership(config_dir)
26+
self.create_directory_with_ownership(cache_dir)
4527

4628
return config_dir, cache_dir
4729

48-
def initialize(self) -> bool:
30+
def is_already_configured(self) -> bool:
31+
config_dir = self._get_aider_config_dir()
32+
env_file = config_dir / ".env"
33+
return env_file.exists()
34+
35+
def configure(self) -> bool:
4936
self.status.log("Setting up Aider configuration...")
5037

51-
# Ensure Aider directories exist
5238
config_dir, cache_dir = self._ensure_aider_dirs()
5339

54-
# Set up environment variables for the session
5540
env_vars = self._create_environment_config()
5641

57-
# Create .env file if we have API keys
5842
if env_vars:
5943
env_file = config_dir / ".env"
6044
success = self._write_env_file(env_file, env_vars)
@@ -71,22 +55,26 @@ def initialize(self) -> bool:
7155
"INFO",
7256
)
7357

74-
# Always return True to allow container to start
58+
if not cubbi_config.mcps:
59+
self.status.log("No MCP servers to integrate")
60+
return True
61+
62+
self.status.log(
63+
f"Found {len(cubbi_config.mcps)} MCP server(s) - no direct integration available for Aider"
64+
)
65+
7566
return True
7667

77-
def _create_environment_config(self) -> Dict[str, str]:
68+
def _create_environment_config(self) -> dict[str, str]:
7869
env_vars = {}
7970

80-
# Configure Aider with the default model from cubbi config
8171
provider_config = cubbi_config.get_provider_for_default_model()
8272
if provider_config and cubbi_config.defaults.model:
8373
_, model_name = cubbi_config.defaults.model.split("/", 1)
8474

85-
# Set the model for Aider
8675
env_vars["AIDER_MODEL"] = model_name
8776
self.status.log(f"Set Aider model to {model_name}")
8877

89-
# Set provider-specific API key and configuration
9078
if provider_config.type == "anthropic":
9179
env_vars["AIDER_ANTHROPIC_API_KEY"] = provider_config.api_key
9280
self.status.log("Configured Anthropic API key for Aider")
@@ -100,10 +88,7 @@ def _create_environment_config(self) -> Dict[str, str]:
10088
)
10189
self.status.log("Configured OpenAI API key for Aider")
10290

103-
# Note: Aider uses different environment variable names for some providers
104-
# We map cubbi provider types to Aider's expected variable names
10591
elif provider_config.type == "google":
106-
# Aider may expect GEMINI_API_KEY for Google models
10792
env_vars["GEMINI_API_KEY"] = provider_config.api_key
10893
self.status.log("Configured Google/Gemini API key for Aider")
10994

@@ -122,7 +107,6 @@ def _create_environment_config(self) -> Dict[str, str]:
122107
"WARNING",
123108
)
124109

125-
# Fallback to legacy environment variable checking for backward compatibility
126110
api_key_mappings = {
127111
"OPENAI_API_KEY": "AIDER_OPENAI_API_KEY",
128112
"ANTHROPIC_API_KEY": "AIDER_ANTHROPIC_API_KEY",
@@ -138,25 +122,21 @@ def _create_environment_config(self) -> Dict[str, str]:
138122
provider = env_var.replace("_API_KEY", "").lower()
139123
self.status.log(f"Added {provider} API key from environment")
140124

141-
# Check for OpenAI API base URL from legacy environment
142125
openai_url = os.environ.get("OPENAI_URL")
143126
if openai_url:
144127
env_vars["AIDER_OPENAI_API_BASE"] = openai_url
145128
self.status.log(
146129
f"Set OpenAI API base URL to {openai_url} from environment"
147130
)
148131

149-
# Legacy model configuration
150132
model = os.environ.get("AIDER_MODEL")
151133
if model:
152134
env_vars["AIDER_MODEL"] = model
153135
self.status.log(f"Set model to {model} from environment")
154136

155-
# Handle additional API keys from AIDER_API_KEYS
156137
additional_keys = os.environ.get("AIDER_API_KEYS")
157138
if additional_keys:
158139
try:
159-
# Parse format: "provider1=key1,provider2=key2"
160140
for pair in additional_keys.split(","):
161141
if "=" in pair:
162142
provider, key = pair.strip().split("=", 1)
@@ -166,17 +146,14 @@ def _create_environment_config(self) -> Dict[str, str]:
166146
except Exception as e:
167147
self.status.log(f"Failed to parse AIDER_API_KEYS: {e}", "WARNING")
168148

169-
# Add git configuration
170149
auto_commits = os.environ.get("AIDER_AUTO_COMMITS", "true")
171150
if auto_commits.lower() in ["true", "false"]:
172151
env_vars["AIDER_AUTO_COMMITS"] = auto_commits
173152

174-
# Add dark mode setting
175153
dark_mode = os.environ.get("AIDER_DARK_MODE", "false")
176154
if dark_mode.lower() in ["true", "false"]:
177155
env_vars["AIDER_DARK_MODE"] = dark_mode
178156

179-
# Add proxy settings
180157
for proxy_var in ["HTTP_PROXY", "HTTPS_PROXY"]:
181158
value = os.environ.get(proxy_var)
182159
if value:
@@ -185,16 +162,15 @@ def _create_environment_config(self) -> Dict[str, str]:
185162

186163
return env_vars
187164

188-
def _write_env_file(self, env_file: Path, env_vars: Dict[str, str]) -> bool:
165+
def _write_env_file(self, env_file: Path, env_vars: dict[str, str]) -> bool:
189166
try:
190167
content = "\n".join(f"{key}={value}" for key, value in env_vars.items())
191168

192169
with open(env_file, "w") as f:
193170
f.write(content)
194171
f.write("\n")
195172

196-
# Set ownership and secure file permissions (read/write for owner only)
197-
self._set_ownership(env_file)
173+
set_ownership(env_file)
198174
os.chmod(env_file, stat.S_IRUSR | stat.S_IWUSR)
199175

200176
self.status.log(f"Created Aider environment file at {env_file}")
@@ -203,18 +179,5 @@ def _write_env_file(self, env_file: Path, env_vars: Dict[str, str]) -> bool:
203179
self.status.log(f"Failed to write Aider environment file: {e}", "ERROR")
204180
return False
205181

206-
def setup_tool_configuration(self) -> bool:
207-
# Additional tool configuration can be added here if needed
208-
return True
209-
210-
def integrate_mcp_servers(self) -> bool:
211-
if not cubbi_config.mcps:
212-
self.status.log("No MCP servers to integrate")
213-
return True
214182

215-
# Aider doesn't have native MCP support like Claude Code,
216-
# but we could potentially add custom integrations here
217-
self.status.log(
218-
f"Found {len(cubbi_config.mcps)} MCP server(s) - no direct integration available for Aider"
219-
)
220-
return True
183+
PLUGIN_CLASS = AiderPlugin

cubbi/images/claudecode/claudecode_plugin.py

Lines changed: 20 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,36 @@
44
import os
55
import stat
66
from pathlib import Path
7-
from typing import Dict, Optional
87

9-
from cubbi_init import ToolPlugin, cubbi_config
8+
from cubbi_init import ToolPlugin, cubbi_config, set_ownership
109

1110

1211
class ClaudeCodePlugin(ToolPlugin):
1312
@property
1413
def tool_name(self) -> str:
1514
return "claudecode"
1615

17-
def _get_user_ids(self) -> tuple[int, int]:
18-
return cubbi_config.user.uid, cubbi_config.user.gid
19-
20-
def _set_ownership(self, path: Path) -> None:
21-
user_id, group_id = self._get_user_ids()
22-
try:
23-
os.chown(path, user_id, group_id)
24-
except OSError as e:
25-
self.status.log(f"Failed to set ownership for {path}: {e}", "WARNING")
26-
2716
def _get_claude_dir(self) -> Path:
2817
return Path("/home/cubbi/.claude")
2918

30-
def _ensure_claude_dir(self) -> Path:
31-
claude_dir = self._get_claude_dir()
19+
def is_already_configured(self) -> bool:
20+
settings_file = self._get_claude_dir() / "settings.json"
21+
return settings_file.exists()
3222

33-
try:
34-
claude_dir.mkdir(mode=0o700, parents=True, exist_ok=True)
35-
self._set_ownership(claude_dir)
36-
except OSError as e:
37-
self.status.log(
38-
f"Failed to create Claude directory {claude_dir}: {e}", "ERROR"
39-
)
40-
41-
return claude_dir
42-
43-
def initialize(self) -> bool:
23+
def configure(self) -> bool:
4424
self.status.log("Setting up Claude Code authentication...")
4525

46-
# Ensure Claude directory exists
47-
claude_dir = self._ensure_claude_dir()
26+
claude_dir = self.create_directory_with_ownership(self._get_claude_dir())
27+
claude_dir.chmod(0o700)
4828

49-
# Create settings configuration
5029
settings = self._create_settings()
5130

5231
if settings:
5332
settings_file = claude_dir / "settings.json"
5433
success = self._write_settings(settings_file, settings)
5534
if success:
5635
self.status.log("✅ Claude Code authentication configured successfully")
36+
self._integrate_mcp_servers()
5737
return True
5838
else:
5939
return False
@@ -63,53 +43,52 @@ def initialize(self) -> bool:
6343
" Please set ANTHROPIC_API_KEY environment variable", "WARNING"
6444
)
6545
self.status.log(" Claude Code will run without authentication", "INFO")
66-
# Return True to allow container to start without API key
67-
# Users can still use Claude Code with their own authentication methods
46+
self._integrate_mcp_servers()
6847
return True
6948

70-
def _create_settings(self) -> Optional[Dict]:
49+
def _integrate_mcp_servers(self) -> None:
50+
if not cubbi_config.mcps:
51+
self.status.log("No MCP servers to integrate")
52+
return
53+
54+
self.status.log("MCP server integration available for Claude Code")
55+
56+
def _create_settings(self) -> dict | None:
7157
settings = {}
7258

73-
# Get Anthropic provider configuration from cubbi_config
7459
anthropic_provider = None
7560
for provider_name, provider_config in cubbi_config.providers.items():
7661
if provider_config.type == "anthropic":
7762
anthropic_provider = provider_config
7863
break
7964

8065
if not anthropic_provider or not anthropic_provider.api_key:
81-
# Fallback to environment variable for backward compatibility
8266
api_key = os.environ.get("ANTHROPIC_API_KEY")
8367
if not api_key:
8468
return None
8569
settings["apiKey"] = api_key
8670
else:
8771
settings["apiKey"] = anthropic_provider.api_key
8872

89-
# Custom authorization token (optional) - still from environment
9073
auth_token = os.environ.get("ANTHROPIC_AUTH_TOKEN")
9174
if auth_token:
9275
settings["authToken"] = auth_token
9376

94-
# Custom headers (optional) - still from environment
9577
custom_headers = os.environ.get("ANTHROPIC_CUSTOM_HEADERS")
9678
if custom_headers:
9779
try:
98-
# Expect JSON string format
9980
settings["customHeaders"] = json.loads(custom_headers)
10081
except json.JSONDecodeError:
10182
self.status.log(
10283
"⚠️ Invalid ANTHROPIC_CUSTOM_HEADERS format, skipping", "WARNING"
10384
)
10485

105-
# Enterprise integration settings - still from environment
10686
if os.environ.get("CLAUDE_CODE_USE_BEDROCK") == "true":
10787
settings["provider"] = "bedrock"
10888

10989
if os.environ.get("CLAUDE_CODE_USE_VERTEX") == "true":
11090
settings["provider"] = "vertex"
11191

112-
# Network proxy settings - still from environment
11392
http_proxy = os.environ.get("HTTP_PROXY")
11493
https_proxy = os.environ.get("HTTPS_PROXY")
11594
if http_proxy or https_proxy:
@@ -119,11 +98,9 @@ def _create_settings(self) -> Optional[Dict]:
11998
if https_proxy:
12099
settings["proxy"]["https"] = https_proxy
121100

122-
# Telemetry settings - still from environment
123101
if os.environ.get("DISABLE_TELEMETRY") == "true":
124102
settings["telemetry"] = {"enabled": False}
125103

126-
# Tool permissions (allow all by default in Cubbi environment)
127104
settings["permissions"] = {
128105
"tools": {
129106
"read": {"allowed": True},
@@ -137,14 +114,12 @@ def _create_settings(self) -> Optional[Dict]:
137114

138115
return settings
139116

140-
def _write_settings(self, settings_file: Path, settings: Dict) -> bool:
117+
def _write_settings(self, settings_file: Path, settings: dict) -> bool:
141118
try:
142-
# Write settings with secure permissions
143119
with open(settings_file, "w") as f:
144120
json.dump(settings, f, indent=2)
145121

146-
# Set ownership and secure file permissions (read/write for owner only)
147-
self._set_ownership(settings_file)
122+
set_ownership(settings_file)
148123
os.chmod(settings_file, stat.S_IRUSR | stat.S_IWUSR)
149124

150125
self.status.log(f"Created Claude Code settings at {settings_file}")
@@ -153,16 +128,5 @@ def _write_settings(self, settings_file: Path, settings: Dict) -> bool:
153128
self.status.log(f"Failed to write Claude Code settings: {e}", "ERROR")
154129
return False
155130

156-
def setup_tool_configuration(self) -> bool:
157-
# Additional tool configuration can be added here if needed
158-
return True
159131

160-
def integrate_mcp_servers(self) -> bool:
161-
if not cubbi_config.mcps:
162-
self.status.log("No MCP servers to integrate")
163-
return True
164-
165-
# Claude Code has built-in MCP support, so we can potentially
166-
# configure MCP servers in the settings if needed
167-
self.status.log("MCP server integration available for Claude Code")
168-
return True
132+
PLUGIN_CLASS = ClaudeCodePlugin

0 commit comments

Comments
 (0)