tweek 0.3.1__py3-none-any.whl → 0.4.1__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.
- tweek/__init__.py +2 -2
- tweek/audit.py +2 -2
- tweek/cli.py +78 -6605
- tweek/cli_config.py +643 -0
- tweek/cli_configure.py +413 -0
- tweek/cli_core.py +718 -0
- tweek/cli_dry_run.py +390 -0
- tweek/cli_helpers.py +316 -0
- tweek/cli_install.py +1666 -0
- tweek/cli_logs.py +301 -0
- tweek/cli_mcp.py +148 -0
- tweek/cli_memory.py +343 -0
- tweek/cli_plugins.py +748 -0
- tweek/cli_protect.py +564 -0
- tweek/cli_proxy.py +405 -0
- tweek/cli_security.py +236 -0
- tweek/cli_skills.py +289 -0
- tweek/cli_uninstall.py +551 -0
- tweek/cli_vault.py +313 -0
- tweek/config/allowed_dirs.yaml +16 -17
- tweek/config/families.yaml +4 -1
- tweek/config/manager.py +17 -0
- tweek/config/patterns.yaml +29 -5
- tweek/config/templates/config.yaml.template +212 -0
- tweek/config/templates/env.template +45 -0
- tweek/config/templates/overrides.yaml.template +121 -0
- tweek/config/templates/tweek.yaml.template +20 -0
- tweek/config/templates.py +136 -0
- tweek/config/tiers.yaml +5 -4
- tweek/diagnostics.py +112 -32
- tweek/hooks/overrides.py +4 -0
- tweek/hooks/post_tool_use.py +46 -1
- tweek/hooks/pre_tool_use.py +149 -49
- tweek/integrations/openclaw.py +84 -0
- tweek/licensing.py +1 -1
- tweek/mcp/__init__.py +7 -9
- tweek/mcp/clients/chatgpt.py +2 -2
- tweek/mcp/clients/claude_desktop.py +2 -2
- tweek/mcp/clients/gemini.py +2 -2
- tweek/mcp/proxy.py +165 -1
- tweek/memory/provenance.py +438 -0
- tweek/memory/queries.py +2 -0
- tweek/memory/safety.py +23 -4
- tweek/memory/schemas.py +1 -0
- tweek/memory/store.py +101 -71
- tweek/plugins/screening/heuristic_scorer.py +1 -1
- tweek/security/integrity.py +77 -0
- tweek/security/llm_reviewer.py +170 -74
- tweek/security/local_reviewer.py +44 -2
- tweek/security/model_registry.py +73 -7
- tweek/skill_template/overrides-reference.md +1 -1
- tweek/skills/context.py +221 -0
- tweek/skills/scanner.py +2 -2
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/METADATA +8 -7
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/RECORD +60 -38
- tweek/mcp/server.py +0 -320
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/WHEEL +0 -0
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/entry_points.txt +0 -0
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/licenses/NOTICE +0 -0
- {tweek-0.3.1.dist-info → tweek-0.4.1.dist-info}/top_level.txt +0 -0
tweek/cli_protect.py
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tweek CLI Protect / Unprotect Commands
|
|
4
|
+
|
|
5
|
+
Per-tool protection lifecycle:
|
|
6
|
+
tweek protect Interactive wizard
|
|
7
|
+
tweek protect claude-code Install Claude Code hooks
|
|
8
|
+
tweek protect openclaw One-command OpenClaw protection
|
|
9
|
+
tweek protect claude-desktop Configure Claude Desktop MCP
|
|
10
|
+
tweek protect chatgpt Configure ChatGPT Desktop MCP
|
|
11
|
+
tweek protect gemini Configure Gemini CLI MCP
|
|
12
|
+
tweek unprotect [tool] Remove protection from a tool
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from tweek.cli_helpers import (
|
|
23
|
+
console,
|
|
24
|
+
TWEEK_BANNER,
|
|
25
|
+
_has_tweek_hooks,
|
|
26
|
+
_has_tweek_at,
|
|
27
|
+
_detect_all_tools,
|
|
28
|
+
)
|
|
29
|
+
from tweek.cli_install import _install_claude_code_hooks
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# PROTECT GROUP
|
|
34
|
+
# =============================================================================
|
|
35
|
+
|
|
36
|
+
@click.group(
|
|
37
|
+
invoke_without_command=True,
|
|
38
|
+
epilog="""\b
|
|
39
|
+
Examples:
|
|
40
|
+
tweek protect Interactive wizard — detect & protect all tools
|
|
41
|
+
tweek protect --status Show protection status for all tools
|
|
42
|
+
tweek protect claude-code Install Claude Code hooks
|
|
43
|
+
tweek protect openclaw One-command OpenClaw protection
|
|
44
|
+
tweek protect claude-desktop Configure Claude Desktop integration
|
|
45
|
+
tweek protect chatgpt Set up ChatGPT Desktop integration
|
|
46
|
+
tweek protect gemini Configure Gemini CLI integration
|
|
47
|
+
"""
|
|
48
|
+
)
|
|
49
|
+
@click.option("--status", is_flag=True, help="Show protection status for all tools")
|
|
50
|
+
@click.pass_context
|
|
51
|
+
def protect(ctx, status):
|
|
52
|
+
"""Set up Tweek protection for AI tools.
|
|
53
|
+
|
|
54
|
+
When run without a subcommand, launches an interactive wizard
|
|
55
|
+
that auto-detects installed AI tools and offers to protect them.
|
|
56
|
+
"""
|
|
57
|
+
if status:
|
|
58
|
+
_show_protection_status()
|
|
59
|
+
return
|
|
60
|
+
if ctx.invoked_subcommand is None:
|
|
61
|
+
_run_protect_wizard()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@protect.command(
|
|
65
|
+
"openclaw",
|
|
66
|
+
epilog="""\b
|
|
67
|
+
Examples:
|
|
68
|
+
tweek protect openclaw Auto-detect and protect OpenClaw
|
|
69
|
+
tweek protect openclaw --paranoid Maximum security preset
|
|
70
|
+
tweek protect openclaw --port 9999 Custom gateway port
|
|
71
|
+
"""
|
|
72
|
+
)
|
|
73
|
+
@click.option("--port", default=None, type=int,
|
|
74
|
+
help="OpenClaw gateway port (default: auto-detect)")
|
|
75
|
+
@click.option("--paranoid", is_flag=True,
|
|
76
|
+
help="Use paranoid security preset (default: cautious)")
|
|
77
|
+
@click.option("--preset", type=click.Choice(["paranoid", "cautious", "balanced", "trusted"]),
|
|
78
|
+
default=None, help="Security preset to apply")
|
|
79
|
+
def protect_openclaw(port, paranoid, preset):
|
|
80
|
+
"""One-command OpenClaw protection setup.
|
|
81
|
+
|
|
82
|
+
Auto-detects OpenClaw, configures proxy wrapping,
|
|
83
|
+
and starts screening all tool calls through Tweek's
|
|
84
|
+
five-layer defense pipeline.
|
|
85
|
+
"""
|
|
86
|
+
from tweek.integrations.openclaw import (
|
|
87
|
+
detect_openclaw_installation,
|
|
88
|
+
setup_openclaw_protection,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
console.print(TWEEK_BANNER, style="cyan")
|
|
92
|
+
|
|
93
|
+
# Resolve preset
|
|
94
|
+
if paranoid:
|
|
95
|
+
effective_preset = "paranoid"
|
|
96
|
+
elif preset:
|
|
97
|
+
effective_preset = preset
|
|
98
|
+
else:
|
|
99
|
+
effective_preset = "cautious"
|
|
100
|
+
|
|
101
|
+
# Step 1: Detect OpenClaw
|
|
102
|
+
console.print("[cyan]Detecting OpenClaw...[/cyan]")
|
|
103
|
+
openclaw = detect_openclaw_installation()
|
|
104
|
+
|
|
105
|
+
if not openclaw["installed"]:
|
|
106
|
+
console.print()
|
|
107
|
+
console.print("[red]OpenClaw not detected on this system.[/red]")
|
|
108
|
+
console.print()
|
|
109
|
+
console.print("[white]Install OpenClaw first:[/white]")
|
|
110
|
+
console.print(" npm install -g openclaw")
|
|
111
|
+
console.print()
|
|
112
|
+
console.print("[white]Or if OpenClaw is installed in a non-standard location,[/white]")
|
|
113
|
+
console.print("[white]specify the gateway port manually:[/white]")
|
|
114
|
+
console.print(" tweek protect openclaw --port 18789")
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
# Show detection results
|
|
118
|
+
console.print()
|
|
119
|
+
console.print(" [green]OpenClaw detected[/green]")
|
|
120
|
+
|
|
121
|
+
if openclaw["version"]:
|
|
122
|
+
console.print(f" Version: {openclaw['version']}")
|
|
123
|
+
|
|
124
|
+
console.print(f" Gateway: port {openclaw['gateway_port']}", end="")
|
|
125
|
+
if openclaw["gateway_active"]:
|
|
126
|
+
console.print(" [green](running)[/green]")
|
|
127
|
+
elif openclaw["process_running"]:
|
|
128
|
+
console.print(" [yellow](process running, gateway inactive)[/yellow]")
|
|
129
|
+
else:
|
|
130
|
+
console.print(" [white](not running)[/white]")
|
|
131
|
+
|
|
132
|
+
if openclaw["config_path"]:
|
|
133
|
+
console.print(f" Config: {openclaw['config_path']}")
|
|
134
|
+
|
|
135
|
+
console.print()
|
|
136
|
+
|
|
137
|
+
# Step 2: Configure protection
|
|
138
|
+
console.print("[cyan]Configuring Tweek protection...[/cyan]")
|
|
139
|
+
result = setup_openclaw_protection(port=port, preset=effective_preset)
|
|
140
|
+
|
|
141
|
+
if not result.success:
|
|
142
|
+
console.print(f"\n[red]Setup failed: {result.error}[/red]")
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# Show configuration
|
|
146
|
+
console.print(f" Scanner: port {result.scanner_port} -> wrapping OpenClaw gateway")
|
|
147
|
+
console.print(f" Preset: {result.preset} (262 patterns + rate limiting)")
|
|
148
|
+
|
|
149
|
+
# Check for API key
|
|
150
|
+
anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
151
|
+
if anthropic_key:
|
|
152
|
+
console.print(" LLM Review: [green]active[/green] (ANTHROPIC_API_KEY found)")
|
|
153
|
+
else:
|
|
154
|
+
console.print(" LLM Review: [white]available (set ANTHROPIC_API_KEY for semantic analysis)[/white]")
|
|
155
|
+
|
|
156
|
+
# Show warnings
|
|
157
|
+
for warning in result.warnings:
|
|
158
|
+
console.print(f"\n [yellow]Warning: {warning}[/yellow]")
|
|
159
|
+
|
|
160
|
+
console.print()
|
|
161
|
+
|
|
162
|
+
if not openclaw["gateway_active"]:
|
|
163
|
+
console.print("[yellow]Note: OpenClaw gateway is not currently running.[/yellow]")
|
|
164
|
+
console.print("[white]Protection will activate when OpenClaw starts.[/white]")
|
|
165
|
+
console.print()
|
|
166
|
+
|
|
167
|
+
console.print("[green]Protection configured.[/green] Screening all OpenClaw tool calls.")
|
|
168
|
+
console.print()
|
|
169
|
+
console.print("[white]Verify: tweek doctor[/white]")
|
|
170
|
+
console.print("[white]Logs: tweek logs show[/white]")
|
|
171
|
+
console.print("[white]Stop: tweek proxy stop[/white]")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@protect.command(
|
|
175
|
+
"claude-code",
|
|
176
|
+
epilog="""\b
|
|
177
|
+
Examples:
|
|
178
|
+
tweek protect claude-code Install for current project
|
|
179
|
+
tweek protect claude-code --global Install globally (all projects)
|
|
180
|
+
tweek protect claude-code --quick Zero-prompt install with defaults
|
|
181
|
+
tweek protect claude-code --preset paranoid Apply paranoid security preset
|
|
182
|
+
"""
|
|
183
|
+
)
|
|
184
|
+
@click.option("--global", "install_global", is_flag=True, default=False,
|
|
185
|
+
help="Install globally to ~/.claude/ (protects all projects)")
|
|
186
|
+
@click.option("--dev-test", is_flag=True, hidden=True,
|
|
187
|
+
help="Install to test environment (for Tweek development only)")
|
|
188
|
+
@click.option("--backup/--no-backup", default=True,
|
|
189
|
+
help="Backup existing hooks before installation")
|
|
190
|
+
@click.option("--skip-env-scan", is_flag=True,
|
|
191
|
+
help="Skip scanning for .env files to migrate")
|
|
192
|
+
@click.option("--interactive", "-i", is_flag=True,
|
|
193
|
+
help="Interactively configure security settings")
|
|
194
|
+
@click.option("--preset", type=click.Choice(["paranoid", "cautious", "balanced", "trusted"]),
|
|
195
|
+
help="Apply a security preset (skip interactive)")
|
|
196
|
+
@click.option("--ai-defaults", is_flag=True,
|
|
197
|
+
help="Let AI suggest default settings based on detected skills")
|
|
198
|
+
@click.option("--with-sandbox", is_flag=True,
|
|
199
|
+
help="Prompt to install sandbox tool if not available (Linux only)")
|
|
200
|
+
@click.option("--force-proxy", is_flag=True,
|
|
201
|
+
help="Force Tweek proxy to override existing proxy configurations (e.g., openclaw)")
|
|
202
|
+
@click.option("--skip-proxy-check", is_flag=True,
|
|
203
|
+
help="Skip checking for existing proxy configurations")
|
|
204
|
+
@click.option("--quick", is_flag=True,
|
|
205
|
+
help="Zero-prompt install with balanced defaults (skips env scan and proxy check)")
|
|
206
|
+
def protect_claude_code(install_global, dev_test, backup, skip_env_scan, interactive, preset, ai_defaults, with_sandbox, force_proxy, skip_proxy_check, quick):
|
|
207
|
+
"""Install Tweek hooks for Claude Code.
|
|
208
|
+
|
|
209
|
+
Installs PreToolUse and PostToolUse hooks to screen all
|
|
210
|
+
Claude Code tool calls through Tweek's security pipeline.
|
|
211
|
+
"""
|
|
212
|
+
_install_claude_code_hooks(
|
|
213
|
+
install_global=install_global,
|
|
214
|
+
dev_test=dev_test,
|
|
215
|
+
backup=backup,
|
|
216
|
+
skip_env_scan=skip_env_scan,
|
|
217
|
+
interactive=interactive,
|
|
218
|
+
preset=preset,
|
|
219
|
+
ai_defaults=ai_defaults,
|
|
220
|
+
with_sandbox=with_sandbox,
|
|
221
|
+
force_proxy=force_proxy,
|
|
222
|
+
skip_proxy_check=skip_proxy_check,
|
|
223
|
+
quick=quick,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@protect.command("claude-desktop")
|
|
228
|
+
def protect_claude_desktop():
|
|
229
|
+
"""Configure Tweek as MCP server for Claude Desktop."""
|
|
230
|
+
_protect_mcp_client("claude-desktop")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@protect.command("chatgpt")
|
|
234
|
+
def protect_chatgpt():
|
|
235
|
+
"""Configure Tweek as MCP server for ChatGPT Desktop."""
|
|
236
|
+
_protect_mcp_client("chatgpt")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@protect.command("gemini")
|
|
240
|
+
def protect_gemini():
|
|
241
|
+
"""Configure Tweek as MCP server for Gemini CLI."""
|
|
242
|
+
_protect_mcp_client("gemini")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _protect_mcp_client(client_name: str):
|
|
246
|
+
"""Shared logic for MCP client protection commands."""
|
|
247
|
+
try:
|
|
248
|
+
from tweek.mcp.clients import get_client
|
|
249
|
+
|
|
250
|
+
handler = get_client(client_name)
|
|
251
|
+
result = handler.install()
|
|
252
|
+
|
|
253
|
+
if result.get("success"):
|
|
254
|
+
console.print(f"[green]{result.get('message', 'Installed successfully')}[/green]")
|
|
255
|
+
if result.get("config_path"):
|
|
256
|
+
console.print(f" Config: {result['config_path']}")
|
|
257
|
+
if result.get("backup"):
|
|
258
|
+
console.print(f" Backup: {result['backup']}")
|
|
259
|
+
if result.get("instructions"):
|
|
260
|
+
console.print()
|
|
261
|
+
for line in result["instructions"]:
|
|
262
|
+
console.print(f" {line}")
|
|
263
|
+
else:
|
|
264
|
+
console.print(f"[red]{result.get('error', 'Installation failed')}[/red]")
|
|
265
|
+
except Exception as e:
|
|
266
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# =============================================================================
|
|
270
|
+
# UNPROTECT COMMAND
|
|
271
|
+
# =============================================================================
|
|
272
|
+
|
|
273
|
+
@click.command(
|
|
274
|
+
epilog="""\b
|
|
275
|
+
Examples:
|
|
276
|
+
tweek unprotect Interactive — choose what to unprotect
|
|
277
|
+
tweek unprotect claude-code Remove Claude Code hooks
|
|
278
|
+
tweek unprotect claude-code --global Remove global Claude Code hooks
|
|
279
|
+
tweek unprotect claude-desktop Remove from Claude Desktop
|
|
280
|
+
tweek unprotect openclaw Remove OpenClaw protection
|
|
281
|
+
"""
|
|
282
|
+
)
|
|
283
|
+
@click.argument("tool", required=False, type=click.Choice(
|
|
284
|
+
["claude-code", "openclaw", "claude-desktop", "chatgpt", "gemini"]))
|
|
285
|
+
@click.option("--global", "unprotect_global", is_flag=True, default=False,
|
|
286
|
+
help="Remove from ~/.claude/ (global installation)")
|
|
287
|
+
@click.option("--confirm", is_flag=True, help="Skip confirmation prompt")
|
|
288
|
+
def unprotect(tool: str, unprotect_global: bool, confirm: bool):
|
|
289
|
+
"""Remove Tweek protection from an AI tool.
|
|
290
|
+
|
|
291
|
+
This removes hooks and MCP configuration for a specific tool
|
|
292
|
+
but keeps Tweek installed on your system. Use `tweek uninstall`
|
|
293
|
+
to fully remove Tweek.
|
|
294
|
+
|
|
295
|
+
When run without arguments, launches an interactive wizard
|
|
296
|
+
that walks through each protected tool asking if you want
|
|
297
|
+
to remove protection.
|
|
298
|
+
|
|
299
|
+
This command can only be run from an interactive terminal.
|
|
300
|
+
AI agents are blocked from running it.
|
|
301
|
+
"""
|
|
302
|
+
from tweek.cli_uninstall import _uninstall_scope
|
|
303
|
+
|
|
304
|
+
# ─────────────────────────────────────────────────────────────
|
|
305
|
+
# HUMAN-ONLY GATE: Block non-interactive execution
|
|
306
|
+
# This is Layer 2 of protection (Layer 1 is the PreToolUse hook)
|
|
307
|
+
# ─────────────────────────────────────────────────────────────
|
|
308
|
+
if not sys.stdin.isatty():
|
|
309
|
+
console.print("[red]ERROR: tweek unprotect must be run from an interactive terminal.[/red]")
|
|
310
|
+
console.print("[white]This command cannot be run by AI agents or automated scripts.[/white]")
|
|
311
|
+
console.print("[white]Open a terminal and run the command directly.[/white]")
|
|
312
|
+
raise SystemExit(1)
|
|
313
|
+
|
|
314
|
+
# No tool: run interactive wizard
|
|
315
|
+
if not tool:
|
|
316
|
+
_run_unprotect_wizard()
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
console.print(TWEEK_BANNER, style="cyan")
|
|
320
|
+
|
|
321
|
+
tweek_dir = Path("~/.tweek").expanduser()
|
|
322
|
+
global_target = Path("~/.claude").expanduser()
|
|
323
|
+
project_target = Path.cwd() / ".claude"
|
|
324
|
+
|
|
325
|
+
if tool == "claude-code":
|
|
326
|
+
if unprotect_global:
|
|
327
|
+
_uninstall_scope(global_target, tweek_dir, confirm, scope_label="global")
|
|
328
|
+
else:
|
|
329
|
+
_uninstall_scope(project_target, tweek_dir, confirm, scope_label="project")
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
if tool in ("claude-desktop", "chatgpt", "gemini"):
|
|
333
|
+
try:
|
|
334
|
+
from tweek.mcp.clients import get_client
|
|
335
|
+
handler = get_client(tool)
|
|
336
|
+
result = handler.uninstall()
|
|
337
|
+
if result.get("success"):
|
|
338
|
+
console.print(f"[green]{result.get('message', 'Uninstalled successfully')}[/green]")
|
|
339
|
+
if result.get("backup"):
|
|
340
|
+
console.print(f" Backup: {result['backup']}")
|
|
341
|
+
if result.get("instructions"):
|
|
342
|
+
console.print()
|
|
343
|
+
for line in result["instructions"]:
|
|
344
|
+
console.print(f" {line}")
|
|
345
|
+
else:
|
|
346
|
+
console.print(f"[red]{result.get('error', 'Uninstallation failed')}[/red]")
|
|
347
|
+
except Exception as e:
|
|
348
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
if tool == "openclaw":
|
|
352
|
+
from tweek.integrations.openclaw import remove_openclaw_protection
|
|
353
|
+
result = remove_openclaw_protection()
|
|
354
|
+
if result.get("success"):
|
|
355
|
+
console.print(f"[green]{result.get('message', 'OpenClaw protection removed')}[/green]")
|
|
356
|
+
for detail in result.get("details", []):
|
|
357
|
+
console.print(f" [green]\u2713[/green] {detail}")
|
|
358
|
+
else:
|
|
359
|
+
console.print(f"[red]{result.get('error', 'Failed to remove OpenClaw protection')}[/red]")
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
# =============================================================================
|
|
364
|
+
# PROTECT WIZARD & STATUS HELPERS
|
|
365
|
+
# =============================================================================
|
|
366
|
+
|
|
367
|
+
def _run_protect_wizard():
|
|
368
|
+
"""Interactive wizard: detect tools and ask Y/n for each one."""
|
|
369
|
+
console.print(TWEEK_BANNER, style="cyan")
|
|
370
|
+
console.print("[bold]Tweek Protection Wizard[/bold]\n")
|
|
371
|
+
console.print("Scanning for AI tools...\n")
|
|
372
|
+
|
|
373
|
+
tools = _detect_all_tools()
|
|
374
|
+
|
|
375
|
+
# Show detection summary
|
|
376
|
+
detected = [(tid, label, prot) for tid, label, inst, prot, _ in tools if inst]
|
|
377
|
+
not_detected = [label for _, label, inst, _, _ in tools if not inst]
|
|
378
|
+
|
|
379
|
+
if not_detected:
|
|
380
|
+
for label in not_detected:
|
|
381
|
+
console.print(f" [white]{label:<20}[/white] [white]not found[/white]")
|
|
382
|
+
|
|
383
|
+
if not detected:
|
|
384
|
+
console.print("\n[yellow]No AI tools detected on this system.[/yellow]")
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Show already-protected tools
|
|
388
|
+
already_protected = [(tid, label) for tid, label, prot in detected if prot]
|
|
389
|
+
unprotected = [(tid, label) for tid, label, prot in detected if not prot]
|
|
390
|
+
|
|
391
|
+
for _, label in already_protected:
|
|
392
|
+
console.print(f" [green]{label:<20} protected[/green]")
|
|
393
|
+
|
|
394
|
+
if not unprotected:
|
|
395
|
+
console.print(f"\n[green]All {len(already_protected)} detected tool(s) already protected.[/green]")
|
|
396
|
+
console.print("Run 'tweek status' to see details.")
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
for _, label in unprotected:
|
|
400
|
+
console.print(f" [yellow]{label:<20} not protected[/yellow]")
|
|
401
|
+
|
|
402
|
+
# Ask for preset first (applies to all)
|
|
403
|
+
console.print()
|
|
404
|
+
console.print("[bold]Security preset:[/bold]")
|
|
405
|
+
console.print(" [bold]1.[/bold] cautious [white](recommended)[/white] \u2014 screen risky & dangerous tools")
|
|
406
|
+
console.print(" [bold]2.[/bold] paranoid \u2014 screen everything except safe tools")
|
|
407
|
+
console.print(" [bold]3.[/bold] trusted \u2014 only screen dangerous tools")
|
|
408
|
+
console.print()
|
|
409
|
+
preset_choice = click.prompt("Select preset", type=click.IntRange(1, 3), default=1)
|
|
410
|
+
preset = ["cautious", "paranoid", "trusted"][preset_choice - 1]
|
|
411
|
+
|
|
412
|
+
# Walk through each unprotected tool
|
|
413
|
+
console.print()
|
|
414
|
+
protected_count = 0
|
|
415
|
+
skipped_count = 0
|
|
416
|
+
|
|
417
|
+
for tool_id, label in unprotected:
|
|
418
|
+
protect_it = click.confirm(f" Protect {label}?", default=True)
|
|
419
|
+
|
|
420
|
+
if not protect_it:
|
|
421
|
+
console.print(f" [white]skipped[/white]")
|
|
422
|
+
skipped_count += 1
|
|
423
|
+
continue
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
if tool_id == "claude-code":
|
|
427
|
+
_install_claude_code_hooks(
|
|
428
|
+
install_global=True, dev_test=False, backup=True,
|
|
429
|
+
skip_env_scan=True, interactive=False, preset=preset,
|
|
430
|
+
ai_defaults=False, with_sandbox=False, force_proxy=False,
|
|
431
|
+
skip_proxy_check=True, quick=True,
|
|
432
|
+
)
|
|
433
|
+
elif tool_id == "openclaw":
|
|
434
|
+
from tweek.integrations.openclaw import setup_openclaw_protection
|
|
435
|
+
result = setup_openclaw_protection(preset=preset)
|
|
436
|
+
if result.success:
|
|
437
|
+
console.print(f" [green]done[/green]")
|
|
438
|
+
else:
|
|
439
|
+
console.print(f" [red]failed: {result.error}[/red]")
|
|
440
|
+
continue
|
|
441
|
+
elif tool_id in ("claude-desktop", "chatgpt", "gemini"):
|
|
442
|
+
_protect_mcp_client(tool_id)
|
|
443
|
+
protected_count += 1
|
|
444
|
+
except Exception as e:
|
|
445
|
+
console.print(f" [red]error: {e}[/red]")
|
|
446
|
+
|
|
447
|
+
console.print()
|
|
448
|
+
if protected_count:
|
|
449
|
+
console.print(f"[green]Protected {protected_count} tool(s).[/green]", end="")
|
|
450
|
+
if skipped_count:
|
|
451
|
+
console.print(f" [white]Skipped {skipped_count}.[/white]", end="")
|
|
452
|
+
console.print()
|
|
453
|
+
console.print("Run 'tweek status' to see the full dashboard.")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _run_unprotect_wizard():
|
|
457
|
+
"""Interactive wizard: detect protected tools and ask Y/n to unprotect each."""
|
|
458
|
+
from tweek.cli_uninstall import _uninstall_scope
|
|
459
|
+
|
|
460
|
+
console.print(TWEEK_BANNER, style="cyan")
|
|
461
|
+
console.print("[bold]Tweek Unprotect Wizard[/bold]\n")
|
|
462
|
+
console.print("Scanning for protected AI tools...\n")
|
|
463
|
+
|
|
464
|
+
tools = _detect_all_tools()
|
|
465
|
+
tweek_dir = Path("~/.tweek").expanduser()
|
|
466
|
+
global_target = Path("~/.claude").expanduser()
|
|
467
|
+
|
|
468
|
+
protected = [(tid, label) for tid, label, inst, prot, _ in tools if inst and prot]
|
|
469
|
+
|
|
470
|
+
if not protected:
|
|
471
|
+
console.print("[yellow]No protected tools found.[/yellow]")
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
for _, label in protected:
|
|
475
|
+
console.print(f" [green]{label:<20} protected[/green]")
|
|
476
|
+
|
|
477
|
+
console.print()
|
|
478
|
+
removed_count = 0
|
|
479
|
+
skipped_count = 0
|
|
480
|
+
|
|
481
|
+
for tool_id, label in protected:
|
|
482
|
+
remove_it = click.confirm(f" Remove protection from {label}?", default=False)
|
|
483
|
+
|
|
484
|
+
if not remove_it:
|
|
485
|
+
console.print(f" [white]kept[/white]")
|
|
486
|
+
skipped_count += 1
|
|
487
|
+
continue
|
|
488
|
+
|
|
489
|
+
try:
|
|
490
|
+
if tool_id == "claude-code":
|
|
491
|
+
_uninstall_scope(global_target, tweek_dir, confirm=True, scope_label="global")
|
|
492
|
+
elif tool_id in ("claude-desktop", "chatgpt", "gemini"):
|
|
493
|
+
from tweek.mcp.clients import get_client
|
|
494
|
+
handler = get_client(tool_id)
|
|
495
|
+
result = handler.uninstall()
|
|
496
|
+
if result.get("success"):
|
|
497
|
+
console.print(f" [green]{result.get('message', 'removed')}[/green]")
|
|
498
|
+
else:
|
|
499
|
+
console.print(f" [red]{result.get('error', 'failed')}[/red]")
|
|
500
|
+
continue
|
|
501
|
+
elif tool_id == "openclaw":
|
|
502
|
+
from tweek.integrations.openclaw import remove_openclaw_protection
|
|
503
|
+
result = remove_openclaw_protection()
|
|
504
|
+
if result.get("success"):
|
|
505
|
+
console.print(f" [green]{result.get('message', 'removed')}[/green]")
|
|
506
|
+
else:
|
|
507
|
+
console.print(f" [red]{result.get('error', 'failed')}[/red]")
|
|
508
|
+
continue
|
|
509
|
+
removed_count += 1
|
|
510
|
+
except Exception as e:
|
|
511
|
+
console.print(f" [red]error: {e}[/red]")
|
|
512
|
+
|
|
513
|
+
console.print()
|
|
514
|
+
if removed_count:
|
|
515
|
+
console.print(f"[green]Removed protection from {removed_count} tool(s).[/green]", end="")
|
|
516
|
+
if skipped_count:
|
|
517
|
+
console.print(f" [white]Kept {skipped_count}.[/white]", end="")
|
|
518
|
+
console.print()
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _show_protection_status():
|
|
522
|
+
"""Show protection status dashboard for all AI tools."""
|
|
523
|
+
from rich.table import Table
|
|
524
|
+
|
|
525
|
+
console.print(TWEEK_BANNER, style="cyan")
|
|
526
|
+
|
|
527
|
+
tools = _detect_all_tools()
|
|
528
|
+
|
|
529
|
+
# Build status table
|
|
530
|
+
table = Table(title="Protection Status", show_lines=False)
|
|
531
|
+
table.add_column("Tool", style="cyan", min_width=18)
|
|
532
|
+
table.add_column("Installed", justify="center", min_width=10)
|
|
533
|
+
table.add_column("Protected", justify="center", min_width=10)
|
|
534
|
+
table.add_column("Details")
|
|
535
|
+
|
|
536
|
+
detected_count = 0
|
|
537
|
+
protected_count = 0
|
|
538
|
+
|
|
539
|
+
for tool_id, label, installed, prot, detail in tools:
|
|
540
|
+
if installed:
|
|
541
|
+
detected_count += 1
|
|
542
|
+
if prot:
|
|
543
|
+
protected_count += 1
|
|
544
|
+
|
|
545
|
+
table.add_row(
|
|
546
|
+
label,
|
|
547
|
+
"[green]yes[/green]" if installed else "[white]no[/white]",
|
|
548
|
+
"[green]yes[/green]" if prot else "[yellow]no[/yellow]" if installed else "[white]\u2014[/white]",
|
|
549
|
+
detail,
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
console.print(table)
|
|
553
|
+
console.print()
|
|
554
|
+
|
|
555
|
+
# Summary line
|
|
556
|
+
unprotected_count = detected_count - protected_count
|
|
557
|
+
if detected_count == 0:
|
|
558
|
+
console.print("[yellow]No AI tools detected.[/yellow]")
|
|
559
|
+
elif unprotected_count == 0:
|
|
560
|
+
console.print(f"[green]{protected_count}/{detected_count} detected tools protected.[/green]")
|
|
561
|
+
else:
|
|
562
|
+
console.print(f"[yellow]{protected_count}/{detected_count} detected tools protected. {unprotected_count} unprotected.[/yellow]")
|
|
563
|
+
console.print("[white]Run 'tweek protect' to set up protection.[/white]")
|
|
564
|
+
console.print()
|