tweek 0.1.0__py3-none-any.whl

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.
Files changed (85) hide show
  1. tweek/__init__.py +16 -0
  2. tweek/cli.py +3390 -0
  3. tweek/cli_helpers.py +193 -0
  4. tweek/config/__init__.py +13 -0
  5. tweek/config/allowed_dirs.yaml +23 -0
  6. tweek/config/manager.py +1064 -0
  7. tweek/config/patterns.yaml +751 -0
  8. tweek/config/tiers.yaml +129 -0
  9. tweek/diagnostics.py +589 -0
  10. tweek/hooks/__init__.py +1 -0
  11. tweek/hooks/pre_tool_use.py +861 -0
  12. tweek/integrations/__init__.py +3 -0
  13. tweek/integrations/moltbot.py +243 -0
  14. tweek/licensing.py +398 -0
  15. tweek/logging/__init__.py +9 -0
  16. tweek/logging/bundle.py +350 -0
  17. tweek/logging/json_logger.py +150 -0
  18. tweek/logging/security_log.py +745 -0
  19. tweek/mcp/__init__.py +24 -0
  20. tweek/mcp/approval.py +456 -0
  21. tweek/mcp/approval_cli.py +356 -0
  22. tweek/mcp/clients/__init__.py +37 -0
  23. tweek/mcp/clients/chatgpt.py +112 -0
  24. tweek/mcp/clients/claude_desktop.py +203 -0
  25. tweek/mcp/clients/gemini.py +178 -0
  26. tweek/mcp/proxy.py +667 -0
  27. tweek/mcp/screening.py +175 -0
  28. tweek/mcp/server.py +317 -0
  29. tweek/platform/__init__.py +131 -0
  30. tweek/plugins/__init__.py +835 -0
  31. tweek/plugins/base.py +1080 -0
  32. tweek/plugins/compliance/__init__.py +30 -0
  33. tweek/plugins/compliance/gdpr.py +333 -0
  34. tweek/plugins/compliance/gov.py +324 -0
  35. tweek/plugins/compliance/hipaa.py +285 -0
  36. tweek/plugins/compliance/legal.py +322 -0
  37. tweek/plugins/compliance/pci.py +361 -0
  38. tweek/plugins/compliance/soc2.py +275 -0
  39. tweek/plugins/detectors/__init__.py +30 -0
  40. tweek/plugins/detectors/continue_dev.py +206 -0
  41. tweek/plugins/detectors/copilot.py +254 -0
  42. tweek/plugins/detectors/cursor.py +192 -0
  43. tweek/plugins/detectors/moltbot.py +205 -0
  44. tweek/plugins/detectors/windsurf.py +214 -0
  45. tweek/plugins/git_discovery.py +395 -0
  46. tweek/plugins/git_installer.py +491 -0
  47. tweek/plugins/git_lockfile.py +338 -0
  48. tweek/plugins/git_registry.py +503 -0
  49. tweek/plugins/git_security.py +482 -0
  50. tweek/plugins/providers/__init__.py +30 -0
  51. tweek/plugins/providers/anthropic.py +181 -0
  52. tweek/plugins/providers/azure_openai.py +289 -0
  53. tweek/plugins/providers/bedrock.py +248 -0
  54. tweek/plugins/providers/google.py +197 -0
  55. tweek/plugins/providers/openai.py +230 -0
  56. tweek/plugins/scope.py +130 -0
  57. tweek/plugins/screening/__init__.py +26 -0
  58. tweek/plugins/screening/llm_reviewer.py +149 -0
  59. tweek/plugins/screening/pattern_matcher.py +273 -0
  60. tweek/plugins/screening/rate_limiter.py +174 -0
  61. tweek/plugins/screening/session_analyzer.py +159 -0
  62. tweek/proxy/__init__.py +302 -0
  63. tweek/proxy/addon.py +223 -0
  64. tweek/proxy/interceptor.py +313 -0
  65. tweek/proxy/server.py +315 -0
  66. tweek/sandbox/__init__.py +71 -0
  67. tweek/sandbox/executor.py +382 -0
  68. tweek/sandbox/linux.py +278 -0
  69. tweek/sandbox/profile_generator.py +323 -0
  70. tweek/screening/__init__.py +13 -0
  71. tweek/screening/context.py +81 -0
  72. tweek/security/__init__.py +22 -0
  73. tweek/security/llm_reviewer.py +348 -0
  74. tweek/security/rate_limiter.py +682 -0
  75. tweek/security/secret_scanner.py +506 -0
  76. tweek/security/session_analyzer.py +600 -0
  77. tweek/vault/__init__.py +40 -0
  78. tweek/vault/cross_platform.py +251 -0
  79. tweek/vault/keychain.py +288 -0
  80. tweek-0.1.0.dist-info/METADATA +335 -0
  81. tweek-0.1.0.dist-info/RECORD +85 -0
  82. tweek-0.1.0.dist-info/WHEEL +5 -0
  83. tweek-0.1.0.dist-info/entry_points.txt +25 -0
  84. tweek-0.1.0.dist-info/licenses/LICENSE +190 -0
  85. tweek-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Claude Desktop MCP Client Configuration
4
+
5
+ Auto-configures Claude Desktop to use Tweek as an MCP security server.
6
+ Config file: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
7
+ """
8
+
9
+ import json
10
+ import shutil
11
+ import sys
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Optional, Dict, Any
15
+
16
+
17
+ class ClaudeDesktopClient:
18
+ """Manage Tweek MCP integration with Claude Desktop."""
19
+
20
+ CLIENT_NAME = "claude-desktop"
21
+ DISPLAY_NAME = "Claude Desktop"
22
+ SERVER_KEY = "tweek-security"
23
+
24
+ def _get_config_path(self) -> Path:
25
+ """Get Claude Desktop config path based on platform."""
26
+ if sys.platform == "darwin":
27
+ return (
28
+ Path.home()
29
+ / "Library"
30
+ / "Application Support"
31
+ / "Claude"
32
+ / "claude_desktop_config.json"
33
+ )
34
+ elif sys.platform == "win32":
35
+ appdata = Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming"))
36
+ return appdata / "Claude" / "claude_desktop_config.json"
37
+ else:
38
+ # Linux - XDG config
39
+ xdg_config = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config"))
40
+ return xdg_config / "claude" / "claude_desktop_config.json"
41
+
42
+ def _get_tweek_command(self) -> str:
43
+ """Get the path to the tweek executable."""
44
+ # Use the installed tweek command
45
+ tweek_path = shutil.which("tweek")
46
+ if tweek_path:
47
+ return tweek_path
48
+ # Fallback to python -m
49
+ return sys.executable
50
+
51
+ def _get_tweek_args(self) -> list:
52
+ """Get the arguments for the tweek MCP server."""
53
+ tweek_path = shutil.which("tweek")
54
+ if tweek_path:
55
+ return ["mcp", "serve"]
56
+ return ["-m", "tweek.mcp", "serve"]
57
+
58
+ def _build_server_config(self) -> Dict[str, Any]:
59
+ """Build the MCP server configuration entry."""
60
+ return {
61
+ "command": self._get_tweek_command(),
62
+ "args": self._get_tweek_args(),
63
+ "env": {},
64
+ }
65
+
66
+ def _backup_config(self, config_path: Path) -> Optional[Path]:
67
+ """Create a timestamped backup of the config file."""
68
+ if not config_path.exists():
69
+ return None
70
+
71
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
72
+ backup_path = config_path.with_suffix(f".backup_{timestamp}.json")
73
+ shutil.copy2(config_path, backup_path)
74
+ return backup_path
75
+
76
+ def install(self) -> Dict[str, Any]:
77
+ """
78
+ Install Tweek MCP server into Claude Desktop config.
79
+
80
+ Merges with existing config, preserving other MCP servers.
81
+
82
+ Returns:
83
+ Dict with status, message, and any backup info.
84
+ """
85
+ config_path = self._get_config_path()
86
+
87
+ # Load existing config
88
+ existing = {}
89
+ if config_path.exists():
90
+ try:
91
+ existing = json.loads(config_path.read_text())
92
+ except json.JSONDecodeError:
93
+ return {
94
+ "success": False,
95
+ "error": f"Invalid JSON in {config_path}. Fix manually or delete to reset.",
96
+ }
97
+
98
+ # Backup
99
+ backup = self._backup_config(config_path)
100
+
101
+ # Merge Tweek into mcpServers
102
+ existing.setdefault("mcpServers", {})
103
+ existing["mcpServers"][self.SERVER_KEY] = self._build_server_config()
104
+
105
+ # Write config
106
+ config_path.parent.mkdir(parents=True, exist_ok=True)
107
+ config_path.write_text(json.dumps(existing, indent=2))
108
+
109
+ result = {
110
+ "success": True,
111
+ "client": self.DISPLAY_NAME,
112
+ "config_path": str(config_path),
113
+ "server_key": self.SERVER_KEY,
114
+ "message": (
115
+ f"Tweek MCP server installed for {self.DISPLAY_NAME}. "
116
+ f"Restart Claude Desktop to activate."
117
+ ),
118
+ }
119
+ if backup:
120
+ result["backup"] = str(backup)
121
+
122
+ return result
123
+
124
+ def uninstall(self) -> Dict[str, Any]:
125
+ """
126
+ Remove Tweek MCP server from Claude Desktop config.
127
+
128
+ Preserves other MCP servers.
129
+ """
130
+ config_path = self._get_config_path()
131
+
132
+ if not config_path.exists():
133
+ return {
134
+ "success": True,
135
+ "message": f"No config file found at {config_path}. Nothing to remove.",
136
+ }
137
+
138
+ try:
139
+ config = json.loads(config_path.read_text())
140
+ except json.JSONDecodeError:
141
+ return {
142
+ "success": False,
143
+ "error": f"Invalid JSON in {config_path}",
144
+ }
145
+
146
+ servers = config.get("mcpServers", {})
147
+ if self.SERVER_KEY not in servers:
148
+ return {
149
+ "success": True,
150
+ "message": f"Tweek not found in {self.DISPLAY_NAME} config. Nothing to remove.",
151
+ }
152
+
153
+ # Backup and remove
154
+ backup = self._backup_config(config_path)
155
+ del servers[self.SERVER_KEY]
156
+
157
+ config_path.write_text(json.dumps(config, indent=2))
158
+
159
+ result = {
160
+ "success": True,
161
+ "client": self.DISPLAY_NAME,
162
+ "config_path": str(config_path),
163
+ "message": (
164
+ f"Tweek MCP server removed from {self.DISPLAY_NAME}. "
165
+ f"Restart Claude Desktop to apply."
166
+ ),
167
+ }
168
+ if backup:
169
+ result["backup"] = str(backup)
170
+
171
+ return result
172
+
173
+ def status(self) -> Dict[str, Any]:
174
+ """Check if Tweek is installed in Claude Desktop."""
175
+ config_path = self._get_config_path()
176
+
177
+ if not config_path.exists():
178
+ return {
179
+ "installed": False,
180
+ "client": self.DISPLAY_NAME,
181
+ "config_path": str(config_path),
182
+ "message": "Config file not found",
183
+ }
184
+
185
+ try:
186
+ config = json.loads(config_path.read_text())
187
+ servers = config.get("mcpServers", {})
188
+ installed = self.SERVER_KEY in servers
189
+
190
+ return {
191
+ "installed": installed,
192
+ "client": self.DISPLAY_NAME,
193
+ "config_path": str(config_path),
194
+ "server_config": servers.get(self.SERVER_KEY) if installed else None,
195
+ "other_servers": [k for k in servers if k != self.SERVER_KEY],
196
+ }
197
+
198
+ except json.JSONDecodeError:
199
+ return {
200
+ "installed": False,
201
+ "error": "Invalid JSON in config file",
202
+ "config_path": str(config_path),
203
+ }
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Gemini CLI MCP Client Configuration
4
+
5
+ Gemini CLI supports MCP servers via ~/.gemini/settings.json.
6
+ """
7
+
8
+ import json
9
+ import shutil
10
+ import sys
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Dict, Any, Optional
14
+
15
+
16
+ class GeminiClient:
17
+ """Manage Tweek MCP integration with Gemini CLI."""
18
+
19
+ CLIENT_NAME = "gemini"
20
+ DISPLAY_NAME = "Gemini CLI"
21
+ SERVER_KEY = "tweek-security"
22
+
23
+ def _get_config_path(self) -> Path:
24
+ """Get Gemini CLI settings path."""
25
+ return Path.home() / ".gemini" / "settings.json"
26
+
27
+ def _get_tweek_command(self) -> str:
28
+ """Get the path to the tweek executable."""
29
+ tweek_path = shutil.which("tweek")
30
+ if tweek_path:
31
+ return tweek_path
32
+ return sys.executable
33
+
34
+ def _get_tweek_args(self) -> list:
35
+ """Get the arguments for the tweek MCP server."""
36
+ tweek_path = shutil.which("tweek")
37
+ if tweek_path:
38
+ return ["mcp", "serve"]
39
+ return ["-m", "tweek.mcp", "serve"]
40
+
41
+ def _build_server_config(self) -> Dict[str, Any]:
42
+ """Build the MCP server configuration entry."""
43
+ return {
44
+ "command": self._get_tweek_command(),
45
+ "args": self._get_tweek_args(),
46
+ }
47
+
48
+ def _backup_config(self, config_path: Path) -> Optional[Path]:
49
+ """Create a timestamped backup of the config file."""
50
+ if not config_path.exists():
51
+ return None
52
+
53
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
54
+ backup_path = config_path.with_suffix(f".backup_{timestamp}.json")
55
+ shutil.copy2(config_path, backup_path)
56
+ return backup_path
57
+
58
+ def install(self) -> Dict[str, Any]:
59
+ """
60
+ Install Tweek MCP server into Gemini CLI settings.
61
+
62
+ Merges with existing config, preserving other MCP servers.
63
+ """
64
+ config_path = self._get_config_path()
65
+
66
+ # Load existing config
67
+ existing = {}
68
+ if config_path.exists():
69
+ try:
70
+ existing = json.loads(config_path.read_text())
71
+ except json.JSONDecodeError:
72
+ return {
73
+ "success": False,
74
+ "error": f"Invalid JSON in {config_path}. Fix manually or delete to reset.",
75
+ }
76
+
77
+ # Backup
78
+ backup = self._backup_config(config_path)
79
+
80
+ # Merge Tweek into mcpServers
81
+ existing.setdefault("mcpServers", {})
82
+ existing["mcpServers"][self.SERVER_KEY] = self._build_server_config()
83
+
84
+ # Write config
85
+ config_path.parent.mkdir(parents=True, exist_ok=True)
86
+ config_path.write_text(json.dumps(existing, indent=2))
87
+
88
+ result = {
89
+ "success": True,
90
+ "client": self.DISPLAY_NAME,
91
+ "config_path": str(config_path),
92
+ "server_key": self.SERVER_KEY,
93
+ "message": (
94
+ f"Tweek MCP server installed for {self.DISPLAY_NAME}. "
95
+ f"Restart Gemini CLI to activate."
96
+ ),
97
+ }
98
+ if backup:
99
+ result["backup"] = str(backup)
100
+
101
+ return result
102
+
103
+ def uninstall(self) -> Dict[str, Any]:
104
+ """Remove Tweek MCP server from Gemini CLI settings."""
105
+ config_path = self._get_config_path()
106
+
107
+ if not config_path.exists():
108
+ return {
109
+ "success": True,
110
+ "message": f"No config file found at {config_path}. Nothing to remove.",
111
+ }
112
+
113
+ try:
114
+ config = json.loads(config_path.read_text())
115
+ except json.JSONDecodeError:
116
+ return {
117
+ "success": False,
118
+ "error": f"Invalid JSON in {config_path}",
119
+ }
120
+
121
+ servers = config.get("mcpServers", {})
122
+ if self.SERVER_KEY not in servers:
123
+ return {
124
+ "success": True,
125
+ "message": f"Tweek not found in {self.DISPLAY_NAME} config. Nothing to remove.",
126
+ }
127
+
128
+ # Backup and remove
129
+ backup = self._backup_config(config_path)
130
+ del servers[self.SERVER_KEY]
131
+
132
+ config_path.write_text(json.dumps(config, indent=2))
133
+
134
+ result = {
135
+ "success": True,
136
+ "client": self.DISPLAY_NAME,
137
+ "config_path": str(config_path),
138
+ "message": (
139
+ f"Tweek MCP server removed from {self.DISPLAY_NAME}. "
140
+ f"Restart Gemini CLI to apply."
141
+ ),
142
+ }
143
+ if backup:
144
+ result["backup"] = str(backup)
145
+
146
+ return result
147
+
148
+ def status(self) -> Dict[str, Any]:
149
+ """Check if Tweek is installed in Gemini CLI."""
150
+ config_path = self._get_config_path()
151
+
152
+ if not config_path.exists():
153
+ return {
154
+ "installed": False,
155
+ "client": self.DISPLAY_NAME,
156
+ "config_path": str(config_path),
157
+ "message": "Gemini CLI settings not found",
158
+ }
159
+
160
+ try:
161
+ config = json.loads(config_path.read_text())
162
+ servers = config.get("mcpServers", {})
163
+ installed = self.SERVER_KEY in servers
164
+
165
+ return {
166
+ "installed": installed,
167
+ "client": self.DISPLAY_NAME,
168
+ "config_path": str(config_path),
169
+ "server_config": servers.get(self.SERVER_KEY) if installed else None,
170
+ "other_servers": [k for k in servers if k != self.SERVER_KEY],
171
+ }
172
+
173
+ except json.JSONDecodeError:
174
+ return {
175
+ "installed": False,
176
+ "error": "Invalid JSON in settings file",
177
+ "config_path": str(config_path),
178
+ }