tweek 0.2.1__py3-none-any.whl → 0.3.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.
tweek/cli.py CHANGED
@@ -5,7 +5,7 @@ Tweek CLI - GAH! Security for your AI agents.
5
5
  Usage:
6
6
  tweek install [--scope global|project]
7
7
  tweek uninstall [--scope global|project]
8
- tweek status
8
+ tweek doctor
9
9
  tweek config [--skill NAME] [--preset paranoid|cautious|trusted]
10
10
  tweek vault store SKILL KEY VALUE
11
11
  tweek vault get SKILL KEY
@@ -152,41 +152,7 @@ def _has_tweek_hooks(settings: dict) -> bool:
152
152
  return False
153
153
 
154
154
 
155
- @main.command(
156
- epilog="""\b
157
- Examples:
158
- tweek install Install for current project
159
- tweek install --global Install globally (all projects)
160
- tweek install --interactive Walk through configuration prompts
161
- tweek install --preset paranoid Apply paranoid security preset
162
- tweek install --quick Zero-prompt install with defaults
163
- tweek install --with-sandbox Install sandbox tool if needed (Linux)
164
- tweek install --force-proxy Override existing proxy configurations
165
- """
166
- )
167
- @click.option("--global", "install_global", is_flag=True, default=False,
168
- help="Install globally to ~/.claude/ (protects all projects)")
169
- @click.option("--dev-test", is_flag=True, hidden=True,
170
- help="Install to test environment (for Tweek development only)")
171
- @click.option("--backup/--no-backup", default=True,
172
- help="Backup existing hooks before installation")
173
- @click.option("--skip-env-scan", is_flag=True,
174
- help="Skip scanning for .env files to migrate")
175
- @click.option("--interactive", "-i", is_flag=True,
176
- help="Interactively configure security settings")
177
- @click.option("--preset", type=click.Choice(["paranoid", "cautious", "trusted"]),
178
- help="Apply a security preset (skip interactive)")
179
- @click.option("--ai-defaults", is_flag=True,
180
- help="Let AI suggest default settings based on detected skills")
181
- @click.option("--with-sandbox", is_flag=True,
182
- help="Prompt to install sandbox tool if not available (Linux only)")
183
- @click.option("--force-proxy", is_flag=True,
184
- help="Force Tweek proxy to override existing proxy configurations (e.g., openclaw)")
185
- @click.option("--skip-proxy-check", is_flag=True,
186
- help="Skip checking for existing proxy configurations")
187
- @click.option("--quick", is_flag=True,
188
- help="Zero-prompt install with cautious defaults (skips env scan and proxy check)")
189
- def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: bool, interactive: bool, preset: str, ai_defaults: bool, with_sandbox: bool, force_proxy: bool, skip_proxy_check: bool, quick: bool):
155
+ def _install_claude_code_hooks(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: bool, interactive: bool, preset: str, ai_defaults: bool, with_sandbox: bool, force_proxy: bool, skip_proxy_check: bool, quick: bool):
190
156
  """Install Tweek hooks into Claude Code.
191
157
 
192
158
  By default, installs to the current project (./.claude/).
@@ -327,12 +293,12 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
327
293
  if openclaw_status["gateway_active"]:
328
294
  console.print(f" Gateway running on port {openclaw_status['port']}")
329
295
  elif openclaw_status["running"]:
330
- console.print(f" [dim]Process running (gateway may start on port {openclaw_status['port']})[/dim]")
296
+ console.print(f" [white]Process running (gateway may start on port {openclaw_status['port']})[/white]")
331
297
  else:
332
- console.print(f" [dim]Installed but not currently running[/dim]")
298
+ console.print(f" [white]Installed but not currently running[/white]")
333
299
 
334
300
  if openclaw_status["config_path"]:
335
- console.print(f" [dim]Config: {openclaw_status['config_path']}[/dim]")
301
+ console.print(f" [white]Config: {openclaw_status['config_path']}[/white]")
336
302
 
337
303
  console.print()
338
304
 
@@ -344,11 +310,11 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
344
310
  console.print("[cyan]Tweek can protect OpenClaw tool calls. Choose a method:[/cyan]")
345
311
  console.print()
346
312
  console.print(" [cyan]1.[/cyan] Protect via [bold]tweek-security[/bold] ClawHub skill")
347
- console.print(" [dim]Screens tool calls through Tweek as a ClawHub skill[/dim]")
313
+ console.print(" [white]Screens tool calls through Tweek as a ClawHub skill[/white]")
348
314
  console.print(" [cyan]2.[/cyan] Protect via [bold]tweek protect openclaw[/bold]")
349
- console.print(" [dim]Wraps the OpenClaw gateway with Tweek's proxy[/dim]")
315
+ console.print(" [white]Wraps the OpenClaw gateway with Tweek's proxy[/white]")
350
316
  console.print(" [cyan]3.[/cyan] Skip for now")
351
- console.print(" [dim]You can set up OpenClaw protection later[/dim]")
317
+ console.print(" [white]You can set up OpenClaw protection later[/white]")
352
318
  console.print()
353
319
 
354
320
  choice = click.prompt(
@@ -366,12 +332,12 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
366
332
  proxy_override_enabled = True
367
333
  console.print()
368
334
  console.print("[green]✓[/green] OpenClaw proxy protection will be configured")
369
- console.print(f" [dim]Run 'tweek protect openclaw' after installation to complete setup[/dim]")
335
+ console.print(f" [white]Run 'tweek protect openclaw' after installation to complete setup[/white]")
370
336
  console.print()
371
337
  else:
372
338
  console.print()
373
- console.print("[dim]Skipped. Run 'tweek protect openclaw' or add the[/dim]")
374
- console.print("[dim]tweek-security skill later to protect OpenClaw.[/dim]")
339
+ console.print("[white]Skipped. Run 'tweek protect openclaw' or add the[/white]")
340
+ console.print("[white]tweek-security skill later to protect OpenClaw.[/white]")
375
341
  console.print()
376
342
 
377
343
  # Check for other proxy conflicts
@@ -388,7 +354,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
388
354
  # Proxy module not fully available, skip detection
389
355
  pass
390
356
  except Exception as e:
391
- console.print(f"[dim]Warning: Could not check for proxy conflicts: {e}[/dim]")
357
+ console.print(f"[white]Warning: Could not check for proxy conflicts: {e}[/white]")
392
358
 
393
359
  # ─────────────────────────────────────────────────────────────
394
360
  # Step 5: Install hooks into settings.json
@@ -402,7 +368,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
402
368
  if settings_file.exists():
403
369
  backup_path = settings_file.with_suffix(".json.tweek-backup")
404
370
  shutil.copy(settings_file, backup_path)
405
- console.print(f"[dim]Backed up existing settings to {backup_path}[/dim]")
371
+ console.print(f"[white]Backed up existing settings to {backup_path}[/white]")
406
372
 
407
373
  # Create target directory
408
374
  target.mkdir(parents=True, exist_ok=True)
@@ -477,7 +443,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
477
443
  shutil.rmtree(skill_target)
478
444
  shutil.copytree(skill_source, skill_target)
479
445
  console.print(f"[green]✓[/green] Tweek skill installed to: {skill_target}")
480
- console.print(f" [dim]Claude now understands Tweek warnings and commands[/dim]")
446
+ console.print(f" [white]Claude now understands Tweek warnings and commands[/white]")
481
447
 
482
448
  # Add whitelist entry for the skill directory in overrides
483
449
  try:
@@ -513,12 +479,12 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
513
479
  console.print(f"[green]✓[/green] Skill directory whitelisted in overrides")
514
480
 
515
481
  except ImportError:
516
- console.print(f"[dim]Note: PyYAML not available — skill whitelist not added to overrides[/dim]")
482
+ console.print(f"[white]Note: PyYAML not available — skill whitelist not added to overrides[/white]")
517
483
  except Exception as e:
518
- console.print(f"[dim]Warning: Could not update overrides whitelist: {e}[/dim]")
484
+ console.print(f"[white]Warning: Could not update overrides whitelist: {e}[/white]")
519
485
  else:
520
- console.print(f"[dim]Tweek skill source not found — skill not installed[/dim]")
521
- console.print(f" [dim]Skill can be installed manually from the tweek repository[/dim]")
486
+ console.print(f"[white]Tweek skill source not found — skill not installed[/white]")
487
+ console.print(f" [white]Skill can be installed manually from the tweek repository[/white]")
522
488
 
523
489
  # ─────────────────────────────────────────────────────────────
524
490
  # Step 7: Security Configuration
@@ -588,7 +554,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
588
554
 
589
555
  console.print(f"\n[green]✓[/green] Configured {len(unknown_skills)} skills")
590
556
  else:
591
- console.print("[dim]All detected skills already configured[/dim]")
557
+ console.print("[white]All detected skills already configured[/white]")
592
558
 
593
559
  # Apply cautious preset as base
594
560
  cfg.apply_preset("cautious")
@@ -622,7 +588,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
622
588
  else:
623
589
  # Custom: ask about key tools
624
590
  console.print("\n[bold]Configure key tools:[/bold]")
625
- console.print("[dim](safe/default/risky/dangerous)[/dim]\n")
591
+ console.print("[white](safe/default/risky/dangerous)[/white]\n")
626
592
 
627
593
  for tool in ["Bash", "WebFetch", "Edit"]:
628
594
  current = cfg.get_tool_tier(tool)
@@ -641,7 +607,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
641
607
  if not cfg.export_config("user"):
642
608
  cfg.apply_preset("cautious")
643
609
  console.print("\n[green]✓[/green] Applied default [bold]cautious[/bold] security preset")
644
- console.print("[dim]Run 'tweek config interactive' to customize[/dim]")
610
+ console.print("[white]Run 'tweek config interactive' to customize[/white]")
645
611
  install_summary["preset"] = "cautious"
646
612
  else:
647
613
  install_summary["preset"] = "existing"
@@ -663,7 +629,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
663
629
 
664
630
  if env_files:
665
631
  table = Table(title="Found .env Files")
666
- table.add_column("#", style="dim")
632
+ table.add_column("#", style="white")
667
633
  table.add_column("Path")
668
634
  table.add_column("Credentials", justify="right")
669
635
 
@@ -705,7 +671,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
705
671
  )
706
672
 
707
673
  # Show dry-run preview
708
- console.print(f" [dim]Preview - credentials to migrate:[/dim]")
674
+ console.print(f" [white]Preview - credentials to migrate:[/white]")
709
675
  for key in keys:
710
676
  console.print(f" • {key}")
711
677
 
@@ -718,9 +684,9 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
718
684
  except Exception as e:
719
685
  console.print(f" [red]✗[/red] Migration failed: {e}")
720
686
  else:
721
- console.print(f" [dim]Skipped[/dim]")
687
+ console.print(f" [white]Skipped[/white]")
722
688
  else:
723
- console.print("[dim]No .env files with credentials found[/dim]")
689
+ console.print("[white]No .env files with credentials found[/white]")
724
690
 
725
691
  # ─────────────────────────────────────────────────────────────
726
692
  # Step 10: Linux: Prompt for firejail installation
@@ -733,8 +699,8 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
733
699
  prompt_install_firejail(console)
734
700
  else:
735
701
  console.print("\n[yellow]Note:[/yellow] Sandbox (firejail) not installed.")
736
- console.print(f"[dim]Install with: {caps.sandbox_install_hint}[/dim]")
737
- console.print("[dim]Or run 'tweek install --with-sandbox' to install now[/dim]")
702
+ console.print(f"[white]Install with: {caps.sandbox_install_hint}[/white]")
703
+ console.print("[white]Or run 'tweek install --with-sandbox' to install now[/white]")
738
704
 
739
705
  # ─────────────────────────────────────────────────────────────
740
706
  # Step 11: Configure Tweek proxy if override was enabled
@@ -764,7 +730,7 @@ def install(install_global: bool, dev_test: bool, backup: bool, skip_env_scan: b
764
730
  yaml.dump(tweek_config, f, default_flow_style=False)
765
731
 
766
732
  console.print("\n[green]✓[/green] Proxy override configured")
767
- console.print(f" [dim]Config saved to: {proxy_config_path}[/dim]")
733
+ console.print(f" [white]Config saved to: {proxy_config_path}[/white]")
768
734
  console.print(" [yellow]Run 'tweek proxy start' to begin intercepting API calls[/yellow]")
769
735
  install_summary["proxy"] = True
770
736
  except Exception as e:
@@ -852,9 +818,9 @@ def _configure_llm_provider(tweek_dir: Path, interactive: bool, quick: bool) ->
852
818
  console.print()
853
819
  console.print(" [cyan]1.[/cyan] Auto-detect (recommended)")
854
820
  if local_model_ready:
855
- console.print(f" [dim]Local model installed ({local_model_name}) — will use it first[/dim]")
821
+ console.print(f" [white]Local model installed ({local_model_name}) — will use it first[/white]")
856
822
  else:
857
- console.print(" [dim]Uses first available: Local model > Anthropic > OpenAI > Google[/dim]")
823
+ console.print(" [white]Uses first available: Local model > Anthropic > OpenAI > Google[/white]")
858
824
  console.print(" [cyan]2.[/cyan] Anthropic (Claude Haiku)")
859
825
  console.print(" [cyan]3.[/cyan] OpenAI (GPT-4o-mini)")
860
826
  console.print(" [cyan]4.[/cyan] Google (Gemini 2.0 Flash)")
@@ -862,8 +828,8 @@ def _configure_llm_provider(tweek_dir: Path, interactive: bool, quick: bool) ->
862
828
  console.print(" [cyan]6.[/cyan] Disable screening")
863
829
  if not local_model_ready:
864
830
  console.print()
865
- console.print(" [dim]Tip: Run 'tweek model download' to install the local model[/dim]")
866
- console.print(" [dim] (on-device, no API key, ~45MB download)[/dim]")
831
+ console.print(" [white]Tip: Run 'tweek model download' to install the local model[/white]")
832
+ console.print(" [white] (on-device, no API key, ~45MB download)[/white]")
867
833
  console.print()
868
834
 
869
835
  choice = click.prompt("Select", type=click.IntRange(1, 6), default=1)
@@ -883,8 +849,8 @@ def _configure_llm_provider(tweek_dir: Path, interactive: bool, quick: bool) ->
883
849
  # Custom endpoint configuration
884
850
  console.print()
885
851
  console.print("[bold]Custom Endpoint Configuration[/bold]")
886
- console.print("[dim]Most local servers (Ollama, LM Studio, vLLM) and cloud providers[/dim]")
887
- console.print("[dim](Together, Groq, Mistral) expose an OpenAI-compatible API.[/dim]")
852
+ console.print("[white]Most local servers (Ollama, LM Studio, vLLM) and cloud providers[/white]")
853
+ console.print("[white](Together, Groq, Mistral) expose an OpenAI-compatible API.[/white]")
888
854
  console.print()
889
855
 
890
856
  result["provider"] = "openai"
@@ -905,7 +871,7 @@ def _configure_llm_provider(tweek_dir: Path, interactive: bool, quick: bool) ->
905
871
  console.print()
906
872
  elif choice == 6:
907
873
  result["provider"] = "disabled"
908
- console.print("[dim]Screening disabled. Pattern matching and other layers remain active.[/dim]")
874
+ console.print("[white]Screening disabled. Pattern matching and other layers remain active.[/white]")
909
875
  # else: quick mode — leave as auto
910
876
 
911
877
  # Resolve display names for summary
@@ -971,7 +937,7 @@ def _configure_llm_provider(tweek_dir: Path, interactive: bool, quick: bool) ->
971
937
  else:
972
938
  console.print(f"[green]✓[/green] LLM provider configured: {result['provider_display']}")
973
939
  except Exception as e:
974
- console.print(f"[dim]Warning: Could not save LLM config: {e}[/dim]")
940
+ console.print(f"[white]Warning: Could not save LLM config: {e}[/white]")
975
941
  else:
976
942
  if result["provider_display"] and "disabled" not in (result["provider_display"] or ""):
977
943
  console.print(f"[green]✓[/green] LLM provider: {result['provider_display']} ({result.get('model_display', 'auto')})")
@@ -1038,7 +1004,7 @@ def _validate_llm_provider(llm_config: dict) -> None:
1038
1004
  expected_vars = [llm_config["api_key_env"]]
1039
1005
  elif llm_config.get("base_url"):
1040
1006
  # Local endpoints (Ollama etc.) don't need an API key
1041
- console.print(f" [dim]Checking endpoint: {llm_config['base_url']}[/dim]")
1007
+ console.print(f" [white]Checking endpoint: {llm_config['base_url']}[/white]")
1042
1008
  try:
1043
1009
  from tweek.security.llm_reviewer import resolve_provider
1044
1010
  test_provider = resolve_provider(
@@ -1051,10 +1017,10 @@ def _validate_llm_provider(llm_config: dict) -> None:
1051
1017
  console.print(f" [green]✓[/green] Endpoint reachable")
1052
1018
  else:
1053
1019
  console.print(f" [yellow]⚠[/yellow] Could not verify endpoint")
1054
- console.print(f" [dim]Tweek will try this endpoint at runtime[/dim]")
1020
+ console.print(f" [white]Tweek will try this endpoint at runtime[/white]")
1055
1021
  except Exception:
1056
1022
  console.print(f" [yellow]⚠[/yellow] Could not verify endpoint")
1057
- console.print(f" [dim]Tweek will try this endpoint at runtime[/dim]")
1023
+ console.print(f" [white]Tweek will try this endpoint at runtime[/white]")
1058
1024
  return
1059
1025
  else:
1060
1026
  expected_vars = env_var_map.get(provider, [])
@@ -1073,8 +1039,8 @@ def _validate_llm_provider(llm_config: dict) -> None:
1073
1039
  if not found_key:
1074
1040
  var_list = " or ".join(expected_vars)
1075
1041
  console.print(f" [yellow]⚠[/yellow] {var_list} not set in environment")
1076
- console.print(f" [dim]LLM review will be disabled until the key is available.[/dim]")
1077
- console.print(f" [dim]Set it in your shell profile or .env file, then restart Claude Code.[/dim]")
1042
+ console.print(f" [white]LLM review will be disabled until the key is available.[/white]")
1043
+ console.print(f" [white]Set it in your shell profile or .env file, then restart Claude Code.[/white]")
1078
1044
 
1079
1045
  # Offer fallback
1080
1046
  console.print()
@@ -1093,7 +1059,7 @@ def _validate_llm_provider(llm_config: dict) -> None:
1093
1059
  else:
1094
1060
  llm_config["provider_display"] = "disabled (no API key found)"
1095
1061
  llm_config["model_display"] = None
1096
- console.print(f" [dim]No API keys found — LLM review will be disabled[/dim]")
1062
+ console.print(f" [white]No API keys found — LLM review will be disabled[/white]")
1097
1063
 
1098
1064
 
1099
1065
  def _print_install_summary(
@@ -1133,7 +1099,7 @@ def _print_install_summary(
1133
1099
  console.print(f" [green]✓[/green] Hook Python: {hook_python}")
1134
1100
  else:
1135
1101
  console.print(f" [yellow]⚠[/yellow] Hook Python not found: {hook_python}")
1136
- console.print(f" [dim]Run 'tweek install' again if Python was reinstalled[/dim]")
1102
+ console.print(f" [white]Run 'tweek install' again if Python was reinstalled[/white]")
1137
1103
  except (IndexError, KeyError):
1138
1104
  pass
1139
1105
  elif has_pre:
@@ -1169,14 +1135,14 @@ def _print_install_summary(
1169
1135
  elif llm_display and "disabled" not in llm_display:
1170
1136
  console.print(f" [green]✓[/green] LLM reviewer: {llm_display}")
1171
1137
  else:
1172
- console.print(f" [dim]○[/dim] LLM reviewer: {llm_display}")
1138
+ console.print(f" [white]○[/white] LLM reviewer: {llm_display}")
1173
1139
 
1174
1140
  # Sandbox status
1175
1141
  caps = get_capabilities()
1176
1142
  if caps.sandbox_available:
1177
1143
  console.print(f" [green]✓[/green] Sandbox: {caps.sandbox_tool}")
1178
1144
  else:
1179
- console.print(f" [dim]○[/dim] Sandbox: not available ({caps.platform.value})")
1145
+ console.print(f" [white]○[/white] Sandbox: not available ({caps.platform.value})")
1180
1146
 
1181
1147
  # Summary table
1182
1148
  console.print()
@@ -1204,34 +1170,37 @@ def _print_install_summary(
1204
1170
 
1205
1171
  # Next steps
1206
1172
  console.print()
1207
- console.print("[dim]Next steps:[/dim]")
1208
- console.print("[dim] tweek status — Verify installation[/dim]")
1209
- console.print("[dim] tweek update — Get latest attack patterns[/dim]")
1210
- console.print("[dim] tweek config list — See security settings[/dim]")
1173
+ console.print("[white]Next steps:[/white]")
1174
+ console.print("[white] tweek doctor — Verify installation[/white]")
1175
+ console.print("[white] tweek update — Get latest attack patterns[/white]")
1176
+ console.print("[white] tweek config list — See security settings[/white]")
1211
1177
  if proxy_override_enabled:
1212
- console.print("[dim] tweek proxy start — Enable API interception[/dim]")
1178
+ console.print("[white] tweek proxy start — Enable API interception[/white]")
1213
1179
 
1214
1180
 
1215
1181
  @main.command(
1216
1182
  epilog="""\b
1217
1183
  Examples:
1218
- tweek uninstall Interactive — choose what to remove
1219
- tweek uninstall --global Remove global installation directly
1220
- tweek uninstall --everything Remove ALL Tweek data system-wide
1221
- tweek uninstall --confirm Skip confirmation prompt
1184
+ tweek unprotect Interactive — choose what to remove
1185
+ tweek unprotect claude-code Remove Claude Code hooks
1186
+ tweek unprotect claude-code --global Remove global Claude Code hooks
1187
+ tweek unprotect claude-desktop Remove from Claude Desktop
1188
+ tweek unprotect --all Remove ALL Tweek data system-wide
1222
1189
  """
1223
1190
  )
1224
- @click.option("--global", "uninstall_global", is_flag=True, default=False,
1225
- help="Uninstall from ~/.claude/ (global installation)")
1226
- @click.option("--everything", is_flag=True, default=False,
1191
+ @click.argument("tool", required=False, type=click.Choice(
1192
+ ["claude-code", "openclaw", "claude-desktop", "chatgpt", "gemini"]))
1193
+ @click.option("--all", "remove_all", is_flag=True, default=False,
1227
1194
  help="Remove ALL Tweek data: hooks, skills, config, patterns, logs, MCP integrations")
1195
+ @click.option("--global", "unprotect_global", is_flag=True, default=False,
1196
+ help="Remove from ~/.claude/ (global installation)")
1228
1197
  @click.option("--confirm", is_flag=True, help="Skip confirmation prompt")
1229
- def uninstall(uninstall_global: bool, everything: bool, confirm: bool):
1230
- """Remove Tweek hooks and data from Claude Code.
1198
+ def unprotect(tool: str, remove_all: bool, unprotect_global: bool, confirm: bool):
1199
+ """Remove Tweek protection from an AI tool.
1231
1200
 
1232
- When run without flags, presents an interactive menu to choose scope.
1233
- Use --global to remove from ~/.claude/ directly.
1234
- Use --everything to remove ALL Tweek data system-wide.
1201
+ When run without arguments, launches an interactive wizard
1202
+ that walks through each protected tool asking if you want
1203
+ to remove protection. Use --all to remove everything at once.
1235
1204
 
1236
1205
  This command can only be run from an interactive terminal.
1237
1206
  AI agents are blocked from running it.
@@ -1243,64 +1212,71 @@ def uninstall(uninstall_global: bool, everything: bool, confirm: bool):
1243
1212
  # This is Layer 2 of protection (Layer 1 is the PreToolUse hook)
1244
1213
  # ─────────────────────────────────────────────────────────────
1245
1214
  if not sys.stdin.isatty():
1246
- console.print("[red]ERROR: tweek uninstall must be run from an interactive terminal.[/red]")
1247
- console.print("[dim]This command cannot be run by AI agents or automated scripts.[/dim]")
1248
- console.print("[dim]Open a terminal and run the command directly.[/dim]")
1215
+ console.print("[red]ERROR: tweek unprotect must be run from an interactive terminal.[/red]")
1216
+ console.print("[white]This command cannot be run by AI agents or automated scripts.[/white]")
1217
+ console.print("[white]Open a terminal and run the command directly.[/white]")
1249
1218
  raise SystemExit(1)
1250
1219
 
1220
+ # No tool and no --all: run interactive wizard
1221
+ if not tool and not remove_all:
1222
+ _run_unprotect_wizard()
1223
+ return
1224
+
1251
1225
  console.print(TWEEK_BANNER, style="cyan")
1252
1226
 
1253
1227
  tweek_dir = Path("~/.tweek").expanduser()
1254
1228
  global_target = Path("~/.claude").expanduser()
1255
1229
  project_target = Path.cwd() / ".claude"
1256
1230
 
1257
- if everything:
1231
+ if remove_all:
1258
1232
  _uninstall_everything(global_target, project_target, tweek_dir, confirm)
1259
- elif uninstall_global:
1260
- _uninstall_scope(global_target, tweek_dir, confirm, scope_label="global")
1261
- else:
1262
- # ── Interactive scope selection ──
1263
- # Detect what's installed
1264
- has_project = _has_tweek_at(project_target)
1265
- has_global = _has_tweek_at(global_target)
1266
- has_data = tweek_dir.exists() and any(tweek_dir.iterdir()) if tweek_dir.exists() else False
1267
-
1268
- if not has_project and not has_global and not has_data:
1269
- console.print("[yellow]No Tweek installation found.[/yellow]")
1270
- console.print(f" Checked project: {project_target}")
1271
- console.print(f" Checked global: {global_target}")
1272
- console.print(f" Checked data: {tweek_dir}")
1273
- _show_package_removal_hint()
1274
- return
1233
+ _show_package_removal_hint()
1234
+ return
1275
1235
 
1276
- console.print("[bold]What would you like to remove?[/bold]\n")
1236
+ if tool == "claude-code":
1237
+ if unprotect_global:
1238
+ _uninstall_scope(global_target, tweek_dir, confirm, scope_label="global")
1239
+ else:
1240
+ _uninstall_scope(project_target, tweek_dir, confirm, scope_label="project")
1241
+ _show_package_removal_hint()
1242
+ return
1277
1243
 
1278
- options = []
1279
- if has_project:
1280
- options.append(("project", f"This project only ({project_target})"))
1281
- if has_global:
1282
- options.append(("global", f"Global installation (~/.claude/)"))
1283
- if has_project or has_global or has_data:
1284
- options.append(("everything", "Everything — all hooks, skills, config, data, and MCP integrations"))
1244
+ if tool in ("claude-desktop", "chatgpt", "gemini"):
1245
+ try:
1246
+ from tweek.mcp.clients import get_client
1247
+ handler = get_client(tool)
1248
+ result = handler.uninstall()
1249
+ if result.get("success"):
1250
+ console.print(f"[green]{result.get('message', 'Uninstalled successfully')}[/green]")
1251
+ if result.get("backup"):
1252
+ console.print(f" Backup: {result['backup']}")
1253
+ if result.get("instructions"):
1254
+ console.print()
1255
+ for line in result["instructions"]:
1256
+ console.print(f" {line}")
1257
+ else:
1258
+ console.print(f"[red]{result.get('error', 'Uninstallation failed')}[/red]")
1259
+ except Exception as e:
1260
+ console.print(f"[red]Error: {e}[/red]")
1261
+ return
1285
1262
 
1286
- for i, (_, label) in enumerate(options, 1):
1287
- console.print(f" [bold]{i}.[/bold] {label}")
1263
+ if tool == "openclaw":
1264
+ console.print("[yellow]OpenClaw unprotect: removing Tweek proxy configuration...[/yellow]")
1265
+ # TODO: implement openclaw unprotect
1266
+ console.print("[white]Manual step: remove tweek plugin from openclaw.json[/white]")
1267
+ return
1288
1268
 
1289
- console.print()
1290
- choice = click.prompt("Select", type=click.IntRange(1, len(options)), default=len(options))
1269
+ _show_package_removal_hint()
1291
1270
 
1292
- selected = options[choice - 1][0]
1293
- console.print()
1294
1271
 
1295
- if selected == "project":
1296
- _uninstall_scope(project_target, tweek_dir, confirm, scope_label="project")
1297
- elif selected == "global":
1298
- _uninstall_scope(global_target, tweek_dir, confirm, scope_label="global")
1299
- elif selected == "everything":
1300
- _uninstall_everything(global_target, project_target, tweek_dir, confirm)
1272
+ @main.command()
1273
+ def status():
1274
+ """Show Tweek protection status dashboard.
1301
1275
 
1302
- # Always show how to remove the CLI binary itself
1303
- _show_package_removal_hint()
1276
+ Scans for all supported AI tools and displays which are
1277
+ detected, which are protected by Tweek, and configuration details.
1278
+ """
1279
+ _show_protection_status()
1304
1280
 
1305
1281
 
1306
1282
  # ─────────────────────────────────────────────────────────────
@@ -1360,9 +1336,9 @@ def _show_package_removal_hint():
1360
1336
  console.print("[bold yellow]The tweek CLI binary is still installed on your system.[/bold yellow]")
1361
1337
 
1362
1338
  if len(pkg_cmds) > 1:
1363
- console.print(f"[dim]Found {len(pkg_cmds)} installations:[/dim]")
1339
+ console.print(f"[white]Found {len(pkg_cmds)} installations:[/white]")
1364
1340
  for cmd in pkg_cmds:
1365
- console.print(f" [dim]• {cmd}[/dim]")
1341
+ console.print(f" [white]• {cmd}[/white]")
1366
1342
 
1367
1343
  console.print()
1368
1344
  label = " + ".join(f"[bold]{cmd}[/bold]" for cmd in pkg_cmds)
@@ -1383,10 +1359,10 @@ def _show_package_removal_hint():
1383
1359
  console.print(f"[green]✓[/green] Removed ({pkg_cmd})")
1384
1360
  else:
1385
1361
  console.print(f"[red]✗[/red] Failed: {result.stderr.strip()}")
1386
- console.print(f" [dim]Run manually: {pkg_cmd}[/dim]")
1362
+ console.print(f" [white]Run manually: {pkg_cmd}[/white]")
1387
1363
  except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
1388
1364
  console.print(f"[red]✗[/red] Could not run: {e}")
1389
- console.print(f" [dim]Run manually: {pkg_cmd}[/dim]")
1365
+ console.print(f" [white]Run manually: {pkg_cmd}[/white]")
1390
1366
 
1391
1367
 
1392
1368
  def _has_tweek_at(target: Path) -> bool:
@@ -1620,18 +1596,18 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1620
1596
  console.print()
1621
1597
  console.print("[bold]The following will be removed:[/bold]")
1622
1598
  if has_hooks:
1623
- console.print(" [dim]•[/dim] PreToolUse and PostToolUse hooks from settings.json")
1599
+ console.print(" [white]•[/white] PreToolUse and PostToolUse hooks from settings.json")
1624
1600
  if has_skills:
1625
- console.print(" [dim]•[/dim] Tweek skill directory (skills/tweek/)")
1601
+ console.print(" [white]•[/white] Tweek skill directory (skills/tweek/)")
1626
1602
  if has_backup:
1627
- console.print(" [dim]•[/dim] Backup file (settings.json.tweek-backup)")
1628
- console.print(" [dim]•[/dim] Project whitelist entries from overrides")
1603
+ console.print(" [white]•[/white] Backup file (settings.json.tweek-backup)")
1604
+ console.print(" [white]•[/white] Project whitelist entries from overrides")
1629
1605
  console.print()
1630
1606
 
1631
1607
  if not confirm:
1632
1608
  console.print(f"[yellow]Remove Tweek from this {scope_label}?[/yellow] ", end="")
1633
1609
  if not click.confirm(""):
1634
- console.print("[dim]Cancelled[/dim]")
1610
+ console.print("[white]Cancelled[/white]")
1635
1611
  return
1636
1612
 
1637
1613
  console.print()
@@ -1647,27 +1623,27 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1647
1623
  if _remove_skill_directory(target):
1648
1624
  console.print(f" [green]✓[/green] Removed Tweek skill directory (skills/tweek/)")
1649
1625
  else:
1650
- console.print(f" [dim]-[/dim] Skipped: Tweek skill directory not found")
1626
+ console.print(f" [white]-[/white] Skipped: Tweek skill directory not found")
1651
1627
 
1652
1628
  # 3. Remove backup file
1653
1629
  if _remove_backup_file(target):
1654
1630
  console.print(f" [green]✓[/green] Removed backup file (settings.json.tweek-backup)")
1655
1631
  else:
1656
- console.print(f" [dim]-[/dim] Skipped: no backup file found")
1632
+ console.print(f" [white]-[/white] Skipped: no backup file found")
1657
1633
 
1658
1634
  # 4. Remove whitelist entries
1659
1635
  wl_count = _remove_whitelist_entries(target, tweek_dir)
1660
1636
  if wl_count > 0:
1661
1637
  console.print(f" [green]✓[/green] Removed {wl_count} whitelist entry(s) from overrides")
1662
1638
  else:
1663
- console.print(f" [dim]-[/dim] Skipped: no whitelist entries found for this {scope_label}")
1639
+ console.print(f" [white]-[/white] Skipped: no whitelist entries found for this {scope_label}")
1664
1640
 
1665
1641
  console.print()
1666
1642
  console.print(f"[green]Uninstall complete.[/green] Tweek is no longer active for this {scope_label}.")
1667
1643
  if scope_label == "project":
1668
- console.print("[dim]Global installation (~/.claude/) was not affected.[/dim]")
1644
+ console.print("[white]Global installation (~/.claude/) was not affected.[/white]")
1669
1645
  else:
1670
- console.print("[dim]Project installations were not affected.[/dim]")
1646
+ console.print("[white]Project installations were not affected.[/white]")
1671
1647
 
1672
1648
  # Offer to remove data directory
1673
1649
  if tweek_dir.exists() and not confirm:
@@ -1680,7 +1656,7 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1680
1656
 
1681
1657
  console.print()
1682
1658
  console.print("[yellow]Also remove Tweek data directory (~/.tweek/)?[/yellow]")
1683
- console.print("[dim]This contains config, patterns, security logs, and overrides.[/dim]")
1659
+ console.print("[white]This contains config, patterns, security logs, and overrides.[/white]")
1684
1660
  if other_has_tweek:
1685
1661
  console.print(f"[bold red]Warning:[/bold red] Tweek is still installed at {other_label} scope ({other_target}).")
1686
1662
  console.print(f" Removing ~/.tweek/ will affect that installation (no config, patterns, or logs).")
@@ -1695,9 +1671,9 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1695
1671
  for item in data_removed:
1696
1672
  console.print(f" [green]✓[/green] Removed {item}")
1697
1673
  if not data_removed:
1698
- console.print(f" [dim]-[/dim] No data to remove")
1674
+ console.print(f" [white]-[/white] No data to remove")
1699
1675
  elif tweek_dir.exists():
1700
- console.print("[dim]Tweek data directory (~/.tweek/) was preserved.[/dim]")
1676
+ console.print("[white]Tweek data directory (~/.tweek/) was preserved.[/white]")
1701
1677
 
1702
1678
 
1703
1679
  def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir: Path, confirm: bool):
@@ -1705,28 +1681,28 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1705
1681
  import json
1706
1682
 
1707
1683
  console.print("[bold yellow]FULL REMOVAL[/bold yellow] — This will remove ALL Tweek data:\n")
1708
- console.print(" [dim]•[/dim] Hooks from current project (.claude/settings.json)")
1709
- console.print(" [dim]•[/dim] Hooks from global installation (~/.claude/settings.json)")
1710
- console.print(" [dim]•[/dim] Tweek skill directories (project + global)")
1711
- console.print(" [dim]•[/dim] All backup files")
1712
- console.print(" [dim]•[/dim] Tweek data directory (~/.tweek/)")
1684
+ console.print(" [white]•[/white] Hooks from current project (.claude/settings.json)")
1685
+ console.print(" [white]•[/white] Hooks from global installation (~/.claude/settings.json)")
1686
+ console.print(" [white]•[/white] Tweek skill directories (project + global)")
1687
+ console.print(" [white]•[/white] All backup files")
1688
+ console.print(" [white]•[/white] Tweek data directory (~/.tweek/)")
1713
1689
 
1714
1690
  # Show what exists in ~/.tweek/
1715
1691
  if tweek_dir.exists():
1716
1692
  for item in sorted(tweek_dir.iterdir()):
1717
1693
  if item.is_dir():
1718
- console.print(f" [dim]├── {item.name}/ [/dim]")
1694
+ console.print(f" [white]├── {item.name}/ [/white]")
1719
1695
  else:
1720
- console.print(f" [dim]├── {item.name}[/dim]")
1696
+ console.print(f" [white]├── {item.name}[/white]")
1721
1697
 
1722
- console.print(" [dim]•[/dim] MCP integrations (Claude Desktop, ChatGPT)")
1698
+ console.print(" [white]•[/white] MCP integrations (Claude Desktop, ChatGPT)")
1723
1699
  console.print()
1724
1700
 
1725
1701
  if not confirm:
1726
1702
  console.print("[bold red]Type 'yes' to confirm full removal[/bold red]: ", end="")
1727
1703
  response = input()
1728
1704
  if response.strip().lower() != "yes":
1729
- console.print("[dim]Cancelled[/dim]")
1705
+ console.print("[white]Cancelled[/white]")
1730
1706
  return
1731
1707
 
1732
1708
  console.print()
@@ -1737,17 +1713,17 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1737
1713
  for hook_type in removed_hooks:
1738
1714
  console.print(f" [green]✓[/green] Removed {hook_type} hook from project settings.json")
1739
1715
  if not removed_hooks:
1740
- console.print(f" [dim]-[/dim] Skipped: no project hooks found")
1716
+ console.print(f" [white]-[/white] Skipped: no project hooks found")
1741
1717
 
1742
1718
  if _remove_skill_directory(project_target):
1743
1719
  console.print(f" [green]✓[/green] Removed Tweek skill from project")
1744
1720
  else:
1745
- console.print(f" [dim]-[/dim] Skipped: no project skill directory")
1721
+ console.print(f" [white]-[/white] Skipped: no project skill directory")
1746
1722
 
1747
1723
  if _remove_backup_file(project_target):
1748
1724
  console.print(f" [green]✓[/green] Removed project backup file")
1749
1725
  else:
1750
- console.print(f" [dim]-[/dim] Skipped: no project backup file")
1726
+ console.print(f" [white]-[/white] Skipped: no project backup file")
1751
1727
 
1752
1728
  console.print()
1753
1729
 
@@ -1757,17 +1733,17 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1757
1733
  for hook_type in removed_hooks:
1758
1734
  console.print(f" [green]✓[/green] Removed {hook_type} hook from global settings.json")
1759
1735
  if not removed_hooks:
1760
- console.print(f" [dim]-[/dim] Skipped: no global hooks found")
1736
+ console.print(f" [white]-[/white] Skipped: no global hooks found")
1761
1737
 
1762
1738
  if _remove_skill_directory(global_target):
1763
1739
  console.print(f" [green]✓[/green] Removed Tweek skill from global installation")
1764
1740
  else:
1765
- console.print(f" [dim]-[/dim] Skipped: no global skill directory")
1741
+ console.print(f" [white]-[/white] Skipped: no global skill directory")
1766
1742
 
1767
1743
  if _remove_backup_file(global_target):
1768
1744
  console.print(f" [green]✓[/green] Removed global backup file")
1769
1745
  else:
1770
- console.print(f" [dim]-[/dim] Skipped: no global backup file")
1746
+ console.print(f" [white]-[/white] Skipped: no global backup file")
1771
1747
 
1772
1748
  console.print()
1773
1749
 
@@ -1777,7 +1753,7 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1777
1753
  for item in data_removed:
1778
1754
  console.print(f" [green]✓[/green] Removed {item}")
1779
1755
  if not data_removed:
1780
- console.print(f" [dim]-[/dim] Skipped: no data directory found")
1756
+ console.print(f" [white]-[/white] Skipped: no data directory found")
1781
1757
 
1782
1758
  console.print()
1783
1759
 
@@ -1787,7 +1763,7 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1787
1763
  for client in mcp_removed:
1788
1764
  console.print(f" [green]✓[/green] Removed {client} MCP integration")
1789
1765
  if not mcp_removed:
1790
- console.print(f" [dim]-[/dim] Skipped: no MCP integrations found")
1766
+ console.print(f" [white]-[/white] Skipped: no MCP integrations found")
1791
1767
 
1792
1768
  console.print()
1793
1769
  console.print("[green]All Tweek data has been removed.[/green]")
@@ -1866,8 +1842,8 @@ def trust(path: str, reason: str, list_trusted: bool):
1866
1842
  ]
1867
1843
 
1868
1844
  if not whitelist:
1869
- console.print("[dim]No trusted paths configured.[/dim]")
1870
- console.print("[dim]Use 'tweek trust' to trust the current project.[/dim]")
1845
+ console.print("[white]No trusted paths configured.[/white]")
1846
+ console.print("[white]Use 'tweek trust' to trust the current project.[/white]")
1871
1847
  return
1872
1848
 
1873
1849
  if trusted_entries:
@@ -1876,16 +1852,16 @@ def trust(path: str, reason: str, list_trusted: bool):
1876
1852
  entry_reason = entry.get("reason", "")
1877
1853
  console.print(f" [green]✓[/green] {entry['path']}")
1878
1854
  if entry_reason:
1879
- console.print(f" [dim]{entry_reason}[/dim]")
1855
+ console.print(f" [white]{entry_reason}[/white]")
1880
1856
 
1881
1857
  if tool_scoped:
1882
1858
  console.print("\n[bold]Tool-scoped whitelist entries:[/bold]\n")
1883
1859
  for entry in tool_scoped:
1884
1860
  tools = ", ".join(entry.get("tools", []))
1885
1861
  entry_reason = entry.get("reason", "")
1886
- console.print(f" [cyan]○[/cyan] {entry['path']} [dim]({tools})[/dim]")
1862
+ console.print(f" [cyan]○[/cyan] {entry['path']} [white]({tools})[/white]")
1887
1863
  if entry_reason:
1888
- console.print(f" [dim]{entry_reason}[/dim]")
1864
+ console.print(f" [white]{entry_reason}[/white]")
1889
1865
 
1890
1866
  if other_entries:
1891
1867
  console.print("\n[bold]Other whitelist entries:[/bold]\n")
@@ -1896,9 +1872,9 @@ def trust(path: str, reason: str, list_trusted: bool):
1896
1872
  console.print(f" [cyan]○[/cyan] Command: {entry['command_prefix']}")
1897
1873
  entry_reason = entry.get("reason", "")
1898
1874
  if entry_reason:
1899
- console.print(f" [dim]{entry_reason}[/dim]")
1875
+ console.print(f" [white]{entry_reason}[/white]")
1900
1876
 
1901
- console.print(f"\n[dim]Config: {overrides_path}[/dim]")
1877
+ console.print(f"\n[white]Config: {overrides_path}[/white]")
1902
1878
  return
1903
1879
 
1904
1880
  # Resolve path to absolute
@@ -1915,7 +1891,7 @@ def trust(path: str, reason: str, list_trusted: bool):
1915
1891
 
1916
1892
  if already_trusted:
1917
1893
  console.print(f"[green]✓[/green] Already trusted: {resolved}")
1918
- console.print("[dim]Use 'tweek untrust' to remove.[/dim]")
1894
+ console.print("[white]Use 'tweek untrust' to remove.[/white]")
1919
1895
  return
1920
1896
 
1921
1897
  # Add whitelist entry (no tools restriction = all tools exempt)
@@ -1933,8 +1909,8 @@ def trust(path: str, reason: str, list_trusted: bool):
1933
1909
  return
1934
1910
 
1935
1911
  console.print(f"[green]✓[/green] Trusted: {resolved}")
1936
- console.print(f" [dim]All screening is now skipped for files in this directory.[/dim]")
1937
- console.print(f" [dim]To resume screening: tweek untrust {path}[/dim]")
1912
+ console.print(f" [white]All screening is now skipped for files in this directory.[/white]")
1913
+ console.print(f" [white]To resume screening: tweek untrust {path}[/white]")
1938
1914
 
1939
1915
 
1940
1916
  @main.command(
@@ -1982,7 +1958,7 @@ def untrust(path: str):
1982
1958
 
1983
1959
  if len(whitelist) == original_len:
1984
1960
  console.print(f"[yellow]This path is not currently trusted:[/yellow] {resolved}")
1985
- console.print("[dim]Use 'tweek trust --list' to see all trusted paths.[/dim]")
1961
+ console.print("[white]Use 'tweek trust --list' to see all trusted paths.[/white]")
1986
1962
  return
1987
1963
 
1988
1964
  overrides["whitelist"] = whitelist
@@ -1998,7 +1974,7 @@ def untrust(path: str):
1998
1974
  return
1999
1975
 
2000
1976
  console.print(f"[green]✓[/green] Removed trust: {resolved}")
2001
- console.print(f" [dim]Tweek will now screen tool calls for files in this directory.[/dim]")
1977
+ console.print(f" [white]Tweek will now screen tool calls for files in this directory.[/white]")
2002
1978
 
2003
1979
 
2004
1980
  @main.command(
@@ -2029,7 +2005,7 @@ def update(check: bool):
2029
2005
  # First time: clone the repo
2030
2006
  if check:
2031
2007
  console.print("[yellow]Patterns not installed.[/yellow]")
2032
- console.print(f"[dim]Run 'tweek update' to install from {patterns_repo}[/dim]")
2008
+ console.print(f"[white]Run 'tweek update' to install from {patterns_repo}[/white]")
2033
2009
  return
2034
2010
 
2035
2011
  console.print(f"[cyan]Installing patterns from {patterns_repo}...[/cyan]")
@@ -2051,15 +2027,15 @@ def update(check: bool):
2051
2027
  data = yaml.safe_load(f)
2052
2028
  count = data.get("pattern_count", len(data.get("patterns", [])))
2053
2029
  free_max = data.get("free_tier_max", 23)
2054
- console.print(f"[dim]Installed {count} patterns ({free_max} free, {count - free_max} pro)[/dim]")
2030
+ console.print(f"[white]Installed {count} patterns ({free_max} free, {count - free_max} pro)[/white]")
2055
2031
 
2056
2032
  except subprocess.CalledProcessError as e:
2057
2033
  console.print(f"[red]✗[/red] Failed to clone patterns: {e.stderr}")
2058
2034
  return
2059
2035
  except FileNotFoundError:
2060
2036
  console.print("[red]\u2717[/red] git not found.")
2061
- console.print(" [dim]Hint: Install git from https://git-scm.com/downloads[/dim]")
2062
- console.print(" [dim]On macOS: xcode-select --install[/dim]")
2037
+ console.print(" [white]Hint: Install git from https://git-scm.com/downloads[/white]")
2038
+ console.print(" [white]On macOS: xcode-select --install[/white]")
2063
2039
  return
2064
2040
 
2065
2041
  else:
@@ -2080,7 +2056,7 @@ def update(check: bool):
2080
2056
  )
2081
2057
  if "behind" in result2.stdout:
2082
2058
  console.print("[yellow]Updates available.[/yellow]")
2083
- console.print("[dim]Run 'tweek update' to install[/dim]")
2059
+ console.print("[white]Run 'tweek update' to install[/white]")
2084
2060
  else:
2085
2061
  console.print("[green]✓[/green] Patterns are up to date")
2086
2062
  except Exception as e:
@@ -2104,11 +2080,11 @@ def update(check: bool):
2104
2080
 
2105
2081
  # Show what changed
2106
2082
  if result.stdout.strip():
2107
- console.print(f"[dim]{result.stdout.strip()}[/dim]")
2083
+ console.print(f"[white]{result.stdout.strip()}[/white]")
2108
2084
 
2109
2085
  except subprocess.CalledProcessError as e:
2110
2086
  console.print(f"[red]✗[/red] Failed to update patterns: {e.stderr}")
2111
- console.print("[dim]Try: rm -rf ~/.tweek/patterns && tweek update[/dim]")
2087
+ console.print("[white]Try: rm -rf ~/.tweek/patterns && tweek update[/white]")
2112
2088
  return
2113
2089
 
2114
2090
  # Show current version info
@@ -2126,7 +2102,7 @@ def update(check: bool):
2126
2102
  console.print(f"[cyan]Total patterns:[/cyan] {count} (all included free)")
2127
2103
 
2128
2104
  console.print(f"[cyan]All features:[/cyan] LLM review, session analysis, rate limiting, sandbox (open source)")
2129
- console.print(f"[dim]Pro (teams) and Enterprise (compliance) coming soon: gettweek.com[/dim]")
2105
+ console.print(f"[white]Pro (teams) and Enterprise (compliance) coming soon: gettweek.com[/white]")
2130
2106
 
2131
2107
  except Exception:
2132
2108
  pass
@@ -2159,22 +2135,6 @@ def doctor(verbose: bool, json_out: bool):
2159
2135
  print_doctor_results(checks)
2160
2136
 
2161
2137
 
2162
- # `tweek status` — alias for `tweek doctor`
2163
- @main.command("status")
2164
- @click.option("--verbose", "-v", is_flag=True, help="Show detailed check information")
2165
- @click.option("--json-output", "--json", "json_out", is_flag=True, help="Output results as JSON")
2166
- def status(verbose: bool, json_out: bool):
2167
- """Show Tweek protection status (alias for 'tweek doctor')."""
2168
- from tweek.diagnostics import run_health_checks
2169
- from tweek.cli_helpers import print_doctor_results, print_doctor_json
2170
-
2171
- checks = run_health_checks(verbose=verbose)
2172
-
2173
- if json_out:
2174
- print_doctor_json(checks)
2175
- else:
2176
- print_doctor_results(checks)
2177
-
2178
2138
 
2179
2139
  @main.command("upgrade")
2180
2140
  def upgrade():
@@ -2340,8 +2300,8 @@ def audit(path, translate, llm_review, json_out):
2340
2300
  skills = scan_installed_skills()
2341
2301
 
2342
2302
  if not skills:
2343
- console.print("[dim]No installed skills found.[/dim]")
2344
- console.print("[dim]Specify a file path to audit: tweek audit <path>[/dim]")
2303
+ console.print("[white]No installed skills found.[/white]")
2304
+ console.print("[white]Specify a file path to audit: tweek audit <path>[/white]")
2345
2305
  return
2346
2306
 
2347
2307
  console.print(f"Found {len(skills)} skill(s)")
@@ -2390,7 +2350,7 @@ def _print_audit_result(result):
2390
2350
  risk_icons = {"safe": "[green]SAFE[/green]", "suspicious": "[yellow]SUSPICIOUS[/yellow]", "dangerous": "[red]DANGEROUS[/red]"}
2391
2351
 
2392
2352
  console.print(f" [bold]{result.skill_name}[/bold] — {risk_icons.get(result.risk_level, result.risk_level)}")
2393
- console.print(f" [dim]{result.skill_path}[/dim]")
2353
+ console.print(f" [white]{result.skill_path}[/white]")
2394
2354
 
2395
2355
  if result.error:
2396
2356
  console.print(f" [red]Error: {result.error}[/red]")
@@ -2405,12 +2365,12 @@ def _print_audit_result(result):
2405
2365
 
2406
2366
  if result.findings:
2407
2367
  table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
2408
- table.add_column("Severity", style="dim")
2368
+ table.add_column("Severity", style="white")
2409
2369
  table.add_column("Pattern")
2410
2370
  table.add_column("Description")
2411
- table.add_column("Match", style="dim")
2371
+ table.add_column("Match", style="white")
2412
2372
 
2413
- severity_styles = {"critical": "red bold", "high": "red", "medium": "yellow", "low": "dim"}
2373
+ severity_styles = {"critical": "red bold", "high": "red", "medium": "yellow", "low": "white"}
2414
2374
 
2415
2375
  for finding in result.findings:
2416
2376
  table.add_row(
@@ -2509,7 +2469,7 @@ def quickstart():
2509
2469
  # Step 2: Security preset
2510
2470
  console.print("[bold cyan]Step 2/4: Security Preset[/bold cyan]")
2511
2471
  console.print(" [cyan]1.[/cyan] paranoid \u2014 Block everything suspicious, prompt on risky")
2512
- console.print(" [cyan]2.[/cyan] cautious \u2014 Block dangerous, prompt on risky [dim](recommended)[/dim]")
2472
+ console.print(" [cyan]2.[/cyan] cautious \u2014 Block dangerous, prompt on risky [white](recommended)[/white]")
2513
2473
  console.print(" [cyan]3.[/cyan] trusted \u2014 Allow most operations, block only dangerous")
2514
2474
  console.print()
2515
2475
 
@@ -2548,17 +2508,16 @@ def quickstart():
2548
2508
  if setup_mcp:
2549
2509
  try:
2550
2510
  import mcp # noqa: F401
2551
- console.print("[dim]MCP package available. Configure upstream servers in ~/.tweek/config.yaml[/dim]")
2552
- console.print("[dim]Then run: tweek mcp proxy[/dim]")
2511
+ console.print("[white]MCP package available. Configure upstream servers in ~/.tweek/config.yaml[/white]")
2512
+ console.print("[white]Then run: tweek mcp proxy[/white]")
2553
2513
  except ImportError:
2554
2514
  print_warning("MCP package not installed. Install with: pip install tweek[mcp]")
2555
2515
  else:
2556
- console.print("[dim]Skipped.[/dim]")
2516
+ console.print("[white]Skipped.[/white]")
2557
2517
 
2558
2518
  console.print()
2559
2519
  console.print("[bold green]Setup complete![/bold green]")
2560
2520
  console.print(" Run [cyan]tweek doctor[/cyan] to verify your installation")
2561
- console.print(" Run [cyan]tweek status[/cyan] to see protection status")
2562
2521
 
2563
2522
 
2564
2523
  def _quickstart_install_hooks(scope: str) -> None:
@@ -2626,21 +2585,31 @@ def _quickstart_install_hooks(scope: str) -> None:
2626
2585
  # =============================================================================
2627
2586
 
2628
2587
  @main.group(
2588
+ invoke_without_command=True,
2629
2589
  epilog="""\b
2630
2590
  Examples:
2631
- tweek protect openclaw One-command OpenClaw protection
2632
- tweek protect openclaw --paranoid Use paranoid security preset
2633
- tweek protect openclaw --port 9999 Override gateway port
2634
- tweek protect claude Install Claude Code hooks (alias for tweek install)
2591
+ tweek protect Interactive wizard detect & protect all tools
2592
+ tweek protect --status Show protection status for all tools
2593
+ tweek protect claude-code Install Claude Code hooks
2594
+ tweek protect openclaw One-command OpenClaw protection
2595
+ tweek protect claude-desktop Configure Claude Desktop integration
2596
+ tweek protect chatgpt Set up ChatGPT Desktop integration
2597
+ tweek protect gemini Configure Gemini CLI integration
2635
2598
  """
2636
2599
  )
2637
- def protect():
2638
- """Set up Tweek protection for a specific AI agent.
2600
+ @click.option("--status", is_flag=True, help="Show protection status for all tools")
2601
+ @click.pass_context
2602
+ def protect(ctx, status):
2603
+ """Set up Tweek protection for AI tools.
2639
2604
 
2640
- One-command setup that auto-detects, configures, and starts
2641
- screening all tool calls for your AI assistant.
2605
+ When run without a subcommand, launches an interactive wizard
2606
+ that auto-detects installed AI tools and offers to protect them.
2642
2607
  """
2643
- pass
2608
+ if status:
2609
+ _show_protection_status()
2610
+ return
2611
+ if ctx.invoked_subcommand is None:
2612
+ _run_protect_wizard()
2644
2613
 
2645
2614
 
2646
2615
  @protect.command(
@@ -2688,11 +2657,11 @@ def protect_openclaw(port, paranoid, preset):
2688
2657
  console.print()
2689
2658
  console.print("[red]OpenClaw not detected on this system.[/red]")
2690
2659
  console.print()
2691
- console.print("[dim]Install OpenClaw first:[/dim]")
2660
+ console.print("[white]Install OpenClaw first:[/white]")
2692
2661
  console.print(" npm install -g openclaw")
2693
2662
  console.print()
2694
- console.print("[dim]Or if OpenClaw is installed in a non-standard location,[/dim]")
2695
- console.print("[dim]specify the gateway port manually:[/dim]")
2663
+ console.print("[white]Or if OpenClaw is installed in a non-standard location,[/white]")
2664
+ console.print("[white]specify the gateway port manually:[/white]")
2696
2665
  console.print(" tweek protect openclaw --port 18789")
2697
2666
  return
2698
2667
 
@@ -2709,7 +2678,7 @@ def protect_openclaw(port, paranoid, preset):
2709
2678
  elif openclaw["process_running"]:
2710
2679
  console.print(" [yellow](process running, gateway inactive)[/yellow]")
2711
2680
  else:
2712
- console.print(" [dim](not running)[/dim]")
2681
+ console.print(" [white](not running)[/white]")
2713
2682
 
2714
2683
  if openclaw["config_path"]:
2715
2684
  console.print(f" Config: {openclaw['config_path']}")
@@ -2733,7 +2702,7 @@ def protect_openclaw(port, paranoid, preset):
2733
2702
  if anthropic_key:
2734
2703
  console.print(" LLM Review: [green]active[/green] (ANTHROPIC_API_KEY found)")
2735
2704
  else:
2736
- console.print(" LLM Review: [dim]available (set ANTHROPIC_API_KEY for semantic analysis)[/dim]")
2705
+ console.print(" LLM Review: [white]available (set ANTHROPIC_API_KEY for semantic analysis)[/white]")
2737
2706
 
2738
2707
  # Show warnings
2739
2708
  for warning in result.warnings:
@@ -2743,53 +2712,366 @@ def protect_openclaw(port, paranoid, preset):
2743
2712
 
2744
2713
  if not openclaw["gateway_active"]:
2745
2714
  console.print("[yellow]Note: OpenClaw gateway is not currently running.[/yellow]")
2746
- console.print("[dim]Protection will activate when OpenClaw starts.[/dim]")
2715
+ console.print("[white]Protection will activate when OpenClaw starts.[/white]")
2747
2716
  console.print()
2748
2717
 
2749
2718
  console.print("[green]Protection configured.[/green] Screening all OpenClaw tool calls.")
2750
2719
  console.print()
2751
- console.print("[dim]Verify: tweek doctor[/dim]")
2752
- console.print("[dim]Logs: tweek logs show[/dim]")
2753
- console.print("[dim]Stop: tweek proxy stop[/dim]")
2720
+ console.print("[white]Verify: tweek doctor[/white]")
2721
+ console.print("[white]Logs: tweek logs show[/white]")
2722
+ console.print("[white]Stop: tweek proxy stop[/white]")
2754
2723
 
2755
2724
 
2756
2725
  @protect.command(
2757
- "claude",
2726
+ "claude-code",
2758
2727
  epilog="""\b
2759
2728
  Examples:
2760
- tweek protect claude Install Claude Code hooks (current project)
2761
- tweek protect claude --global Install globally (all projects)
2729
+ tweek protect claude-code Install for current project
2730
+ tweek protect claude-code --global Install globally (all projects)
2731
+ tweek protect claude-code --quick Zero-prompt install with defaults
2732
+ tweek protect claude-code --preset paranoid Apply paranoid security preset
2762
2733
  """
2763
2734
  )
2764
2735
  @click.option("--global", "install_global", is_flag=True, default=False,
2765
2736
  help="Install globally to ~/.claude/ (protects all projects)")
2737
+ @click.option("--dev-test", is_flag=True, hidden=True,
2738
+ help="Install to test environment (for Tweek development only)")
2739
+ @click.option("--backup/--no-backup", default=True,
2740
+ help="Backup existing hooks before installation")
2741
+ @click.option("--skip-env-scan", is_flag=True,
2742
+ help="Skip scanning for .env files to migrate")
2743
+ @click.option("--interactive", "-i", is_flag=True,
2744
+ help="Interactively configure security settings")
2766
2745
  @click.option("--preset", type=click.Choice(["paranoid", "cautious", "trusted"]),
2767
- default=None, help="Security preset to apply")
2768
- @click.pass_context
2769
- def protect_claude(ctx, install_global, preset):
2746
+ help="Apply a security preset (skip interactive)")
2747
+ @click.option("--ai-defaults", is_flag=True,
2748
+ help="Let AI suggest default settings based on detected skills")
2749
+ @click.option("--with-sandbox", is_flag=True,
2750
+ help="Prompt to install sandbox tool if not available (Linux only)")
2751
+ @click.option("--force-proxy", is_flag=True,
2752
+ help="Force Tweek proxy to override existing proxy configurations (e.g., openclaw)")
2753
+ @click.option("--skip-proxy-check", is_flag=True,
2754
+ help="Skip checking for existing proxy configurations")
2755
+ @click.option("--quick", is_flag=True,
2756
+ help="Zero-prompt install with cautious defaults (skips env scan and proxy check)")
2757
+ def protect_claude_code(install_global, dev_test, backup, skip_env_scan, interactive, preset, ai_defaults, with_sandbox, force_proxy, skip_proxy_check, quick):
2770
2758
  """Install Tweek hooks for Claude Code.
2771
2759
 
2772
- This is equivalent to 'tweek install' -- installs PreToolUse
2773
- and PostToolUse hooks to screen all Claude Code tool calls.
2760
+ Installs PreToolUse and PostToolUse hooks to screen all
2761
+ Claude Code tool calls through Tweek's security pipeline.
2774
2762
  """
2775
- # Delegate to the main install command
2776
- # (use main.commands lookup to avoid name shadowing by mcp install)
2777
- install_cmd = main.commands['install']
2778
- ctx.invoke(
2779
- install_cmd,
2763
+ _install_claude_code_hooks(
2780
2764
  install_global=install_global,
2781
- dev_test=False,
2782
- backup=True,
2783
- skip_env_scan=False,
2784
- interactive=False,
2765
+ dev_test=dev_test,
2766
+ backup=backup,
2767
+ skip_env_scan=skip_env_scan,
2768
+ interactive=interactive,
2785
2769
  preset=preset,
2786
- ai_defaults=False,
2787
- with_sandbox=False,
2788
- force_proxy=False,
2789
- skip_proxy_check=False,
2770
+ ai_defaults=ai_defaults,
2771
+ with_sandbox=with_sandbox,
2772
+ force_proxy=force_proxy,
2773
+ skip_proxy_check=skip_proxy_check,
2774
+ quick=quick,
2790
2775
  )
2791
2776
 
2792
2777
 
2778
+ @protect.command("claude-desktop")
2779
+ def protect_claude_desktop():
2780
+ """Configure Tweek as MCP server for Claude Desktop."""
2781
+ _protect_mcp_client("claude-desktop")
2782
+
2783
+
2784
+ @protect.command("chatgpt")
2785
+ def protect_chatgpt():
2786
+ """Configure Tweek as MCP server for ChatGPT Desktop."""
2787
+ _protect_mcp_client("chatgpt")
2788
+
2789
+
2790
+ @protect.command("gemini")
2791
+ def protect_gemini():
2792
+ """Configure Tweek as MCP server for Gemini CLI."""
2793
+ _protect_mcp_client("gemini")
2794
+
2795
+
2796
+ def _protect_mcp_client(client_name: str):
2797
+ """Shared logic for MCP client protection commands."""
2798
+ try:
2799
+ from tweek.mcp.clients import get_client
2800
+
2801
+ handler = get_client(client_name)
2802
+ result = handler.install()
2803
+
2804
+ if result.get("success"):
2805
+ console.print(f"[green]{result.get('message', 'Installed successfully')}[/green]")
2806
+ if result.get("config_path"):
2807
+ console.print(f" Config: {result['config_path']}")
2808
+ if result.get("backup"):
2809
+ console.print(f" Backup: {result['backup']}")
2810
+ if result.get("instructions"):
2811
+ console.print()
2812
+ for line in result["instructions"]:
2813
+ console.print(f" {line}")
2814
+ else:
2815
+ console.print(f"[red]{result.get('error', 'Installation failed')}[/red]")
2816
+ except Exception as e:
2817
+ console.print(f"[red]Error: {e}[/red]")
2818
+
2819
+
2820
+ # =============================================================================
2821
+ # PROTECT WIZARD & STATUS HELPERS
2822
+ # =============================================================================
2823
+
2824
+
2825
+ def _detect_all_tools():
2826
+ """Detect all supported AI tools and their protection status.
2827
+
2828
+ Returns list of (tool_id, label, installed, protected, detail) tuples.
2829
+ """
2830
+ import shutil
2831
+ import json
2832
+
2833
+ tools = []
2834
+
2835
+ # Claude Code
2836
+ claude_installed = shutil.which("claude") is not None
2837
+ claude_protected = _has_tweek_at(Path("~/.claude").expanduser()) if claude_installed else False
2838
+ tools.append((
2839
+ "claude-code", "Claude Code", claude_installed, claude_protected,
2840
+ "Hooks in ~/.claude/settings.json" if claude_protected else "",
2841
+ ))
2842
+
2843
+ # OpenClaw
2844
+ oc_installed = False
2845
+ oc_protected = False
2846
+ oc_detail = ""
2847
+ try:
2848
+ from tweek.integrations.openclaw import detect_openclaw_installation
2849
+ openclaw = detect_openclaw_installation()
2850
+ oc_installed = openclaw.get("installed", False)
2851
+ if oc_installed:
2852
+ oc_protected = openclaw.get("tweek_configured", False)
2853
+ oc_detail = f"Gateway port {openclaw.get('gateway_port', '?')}"
2854
+ except Exception:
2855
+ pass
2856
+ tools.append(("openclaw", "OpenClaw", oc_installed, oc_protected, oc_detail))
2857
+
2858
+ # MCP clients
2859
+ mcp_configs = [
2860
+ ("claude-desktop", "Claude Desktop",
2861
+ Path("~/Library/Application Support/Claude/claude_desktop_config.json").expanduser()),
2862
+ ("chatgpt", "ChatGPT Desktop",
2863
+ Path("~/Library/Application Support/com.openai.chat/developer_settings.json").expanduser()),
2864
+ ("gemini", "Gemini CLI",
2865
+ Path("~/.gemini/settings.json").expanduser()),
2866
+ ]
2867
+ for tool_id, label, config_path in mcp_configs:
2868
+ installed = config_path.exists()
2869
+ protected = False
2870
+ if installed:
2871
+ try:
2872
+ with open(config_path) as f:
2873
+ data = json.load(f)
2874
+ mcp_servers = data.get("mcpServers", {})
2875
+ protected = "tweek-security" in mcp_servers or "tweek" in mcp_servers
2876
+ except Exception:
2877
+ pass
2878
+ detail = str(config_path) if protected else ""
2879
+ tools.append((tool_id, label, installed, protected, detail))
2880
+
2881
+ return tools
2882
+
2883
+
2884
+ def _run_protect_wizard():
2885
+ """Interactive wizard: detect tools and ask Y/n for each one."""
2886
+ console.print(TWEEK_BANNER, style="cyan")
2887
+ console.print("[bold]Tweek Protection Wizard[/bold]\n")
2888
+ console.print("Scanning for AI tools...\n")
2889
+
2890
+ tools = _detect_all_tools()
2891
+
2892
+ # Show detection summary
2893
+ detected = [(tid, label, prot) for tid, label, inst, prot, _ in tools if inst]
2894
+ not_detected = [label for _, label, inst, _, _ in tools if not inst]
2895
+
2896
+ if not_detected:
2897
+ for label in not_detected:
2898
+ console.print(f" [white]{label:<20}[/white] [white]not found[/white]")
2899
+
2900
+ if not detected:
2901
+ console.print("\n[yellow]No AI tools detected on this system.[/yellow]")
2902
+ return
2903
+
2904
+ # Show already-protected tools
2905
+ already_protected = [(tid, label) for tid, label, prot in detected if prot]
2906
+ unprotected = [(tid, label) for tid, label, prot in detected if not prot]
2907
+
2908
+ for _, label in already_protected:
2909
+ console.print(f" [green]{label:<20} protected[/green]")
2910
+
2911
+ if not unprotected:
2912
+ console.print(f"\n[green]All {len(already_protected)} detected tool(s) already protected.[/green]")
2913
+ console.print("Run 'tweek status' to see details.")
2914
+ return
2915
+
2916
+ for _, label in unprotected:
2917
+ console.print(f" [yellow]{label:<20} not protected[/yellow]")
2918
+
2919
+ # Ask for preset first (applies to all)
2920
+ console.print()
2921
+ console.print("[bold]Security preset:[/bold]")
2922
+ console.print(" [bold]1.[/bold] cautious [white](recommended)[/white] — screen risky & dangerous tools")
2923
+ console.print(" [bold]2.[/bold] paranoid — screen everything except safe tools")
2924
+ console.print(" [bold]3.[/bold] trusted — only screen dangerous tools")
2925
+ console.print()
2926
+ preset_choice = click.prompt("Select preset", type=click.IntRange(1, 3), default=1)
2927
+ preset = ["cautious", "paranoid", "trusted"][preset_choice - 1]
2928
+
2929
+ # Walk through each unprotected tool
2930
+ console.print()
2931
+ protected_count = 0
2932
+ skipped_count = 0
2933
+
2934
+ for tool_id, label in unprotected:
2935
+ protect_it = click.confirm(f" Protect {label}?", default=True)
2936
+
2937
+ if not protect_it:
2938
+ console.print(f" [white]skipped[/white]")
2939
+ skipped_count += 1
2940
+ continue
2941
+
2942
+ try:
2943
+ if tool_id == "claude-code":
2944
+ _install_claude_code_hooks(
2945
+ install_global=True, dev_test=False, backup=True,
2946
+ skip_env_scan=True, interactive=False, preset=preset,
2947
+ ai_defaults=False, with_sandbox=False, force_proxy=False,
2948
+ skip_proxy_check=True, quick=True,
2949
+ )
2950
+ elif tool_id == "openclaw":
2951
+ from tweek.integrations.openclaw import setup_openclaw_protection
2952
+ result = setup_openclaw_protection(preset=preset)
2953
+ if result.success:
2954
+ console.print(f" [green]done[/green]")
2955
+ else:
2956
+ console.print(f" [red]failed: {result.error}[/red]")
2957
+ continue
2958
+ elif tool_id in ("claude-desktop", "chatgpt", "gemini"):
2959
+ _protect_mcp_client(tool_id)
2960
+ protected_count += 1
2961
+ except Exception as e:
2962
+ console.print(f" [red]error: {e}[/red]")
2963
+
2964
+ console.print()
2965
+ if protected_count:
2966
+ console.print(f"[green]Protected {protected_count} tool(s).[/green]", end="")
2967
+ if skipped_count:
2968
+ console.print(f" [white]Skipped {skipped_count}.[/white]", end="")
2969
+ console.print()
2970
+ console.print("Run 'tweek status' to see the full dashboard.")
2971
+
2972
+
2973
+ def _run_unprotect_wizard():
2974
+ """Interactive wizard: detect protected tools and ask Y/n to unprotect each."""
2975
+ console.print(TWEEK_BANNER, style="cyan")
2976
+ console.print("[bold]Tweek Unprotect Wizard[/bold]\n")
2977
+ console.print("Scanning for protected AI tools...\n")
2978
+
2979
+ tools = _detect_all_tools()
2980
+ tweek_dir = Path("~/.tweek").expanduser()
2981
+ global_target = Path("~/.claude").expanduser()
2982
+ project_target = Path.cwd() / ".claude"
2983
+
2984
+ protected = [(tid, label) for tid, label, inst, prot, _ in tools if inst and prot]
2985
+
2986
+ if not protected:
2987
+ console.print("[yellow]No protected tools found.[/yellow]")
2988
+ return
2989
+
2990
+ for _, label in protected:
2991
+ console.print(f" [green]{label:<20} protected[/green]")
2992
+
2993
+ console.print()
2994
+ removed_count = 0
2995
+ skipped_count = 0
2996
+
2997
+ for tool_id, label in protected:
2998
+ remove_it = click.confirm(f" Remove protection from {label}?", default=False)
2999
+
3000
+ if not remove_it:
3001
+ console.print(f" [white]kept[/white]")
3002
+ skipped_count += 1
3003
+ continue
3004
+
3005
+ try:
3006
+ if tool_id == "claude-code":
3007
+ _uninstall_scope(global_target, tweek_dir, confirm=True, scope_label="global")
3008
+ elif tool_id in ("claude-desktop", "chatgpt", "gemini"):
3009
+ from tweek.mcp.clients import get_client
3010
+ handler = get_client(tool_id)
3011
+ result = handler.uninstall()
3012
+ if result.get("success"):
3013
+ console.print(f" [green]{result.get('message', 'removed')}[/green]")
3014
+ else:
3015
+ console.print(f" [red]{result.get('error', 'failed')}[/red]")
3016
+ continue
3017
+ elif tool_id == "openclaw":
3018
+ console.print(" [white]Manual step: remove tweek plugin from openclaw.json[/white]")
3019
+ removed_count += 1
3020
+ except Exception as e:
3021
+ console.print(f" [red]error: {e}[/red]")
3022
+
3023
+ console.print()
3024
+ if removed_count:
3025
+ console.print(f"[green]Removed protection from {removed_count} tool(s).[/green]", end="")
3026
+ if skipped_count:
3027
+ console.print(f" [white]Kept {skipped_count}.[/white]", end="")
3028
+ console.print()
3029
+
3030
+
3031
+ def _show_protection_status():
3032
+ """Show protection status dashboard for all AI tools."""
3033
+ console.print(TWEEK_BANNER, style="cyan")
3034
+
3035
+ tools = _detect_all_tools()
3036
+
3037
+ # Build status table
3038
+ table = Table(title="Protection Status", show_lines=False)
3039
+ table.add_column("Tool", style="cyan", min_width=18)
3040
+ table.add_column("Installed", justify="center", min_width=10)
3041
+ table.add_column("Protected", justify="center", min_width=10)
3042
+ table.add_column("Details")
3043
+
3044
+ detected_count = 0
3045
+ protected_count = 0
3046
+
3047
+ for tool_id, label, installed, protected, detail in tools:
3048
+ if installed:
3049
+ detected_count += 1
3050
+ if protected:
3051
+ protected_count += 1
3052
+
3053
+ table.add_row(
3054
+ label,
3055
+ "[green]yes[/green]" if installed else "[white]no[/white]",
3056
+ "[green]yes[/green]" if protected else "[yellow]no[/yellow]" if installed else "[white]—[/white]",
3057
+ detail,
3058
+ )
3059
+
3060
+ console.print(table)
3061
+ console.print()
3062
+
3063
+ # Summary line
3064
+ unprotected_count = detected_count - protected_count
3065
+ if detected_count == 0:
3066
+ console.print("[yellow]No AI tools detected.[/yellow]")
3067
+ elif unprotected_count == 0:
3068
+ console.print(f"[green]{protected_count}/{detected_count} detected tools protected.[/green]")
3069
+ else:
3070
+ console.print(f"[yellow]{protected_count}/{detected_count} detected tools protected. {unprotected_count} unprotected.[/yellow]")
3071
+ console.print("[white]Run 'tweek protect' to set up protection.[/white]")
3072
+ console.print()
3073
+
3074
+
2793
3075
  # =============================================================================
2794
3076
  # CONFIG COMMANDS
2795
3077
  # =============================================================================
@@ -2874,7 +3156,7 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2874
3156
  }
2875
3157
 
2876
3158
  source_styles = {
2877
- "default": "dim",
3159
+ "default": "white",
2878
3160
  "user": "cyan",
2879
3161
  "project": "magenta",
2880
3162
  }
@@ -2883,7 +3165,7 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2883
3165
  table = Table(title="Tool Security Tiers")
2884
3166
  table.add_column("Tool", style="bold")
2885
3167
  table.add_column("Tier")
2886
- table.add_column("Source", style="dim")
3168
+ table.add_column("Source", style="white")
2887
3169
  table.add_column("Description")
2888
3170
 
2889
3171
  for tool in cfg.list_tools():
@@ -2903,7 +3185,7 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2903
3185
  table = Table(title="Skill Security Tiers")
2904
3186
  table.add_column("Skill", style="bold")
2905
3187
  table.add_column("Tier")
2906
- table.add_column("Source", style="dim")
3188
+ table.add_column("Source", style="white")
2907
3189
  table.add_column("Description")
2908
3190
 
2909
3191
  for skill in cfg.list_skills():
@@ -2918,8 +3200,8 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2918
3200
 
2919
3201
  console.print(table)
2920
3202
 
2921
- console.print("\n[dim]Tiers: safe (no checks) → default (regex) → risky (+LLM) → dangerous (+sandbox)[/dim]")
2922
- console.print("[dim]Sources: default (built-in), user (~/.tweek/config.yaml), project (.tweek/config.yaml)[/dim]")
3203
+ console.print("\n[white]Tiers: safe (no checks) → default (regex) → risky (+LLM) → dangerous (+sandbox)[/white]")
3204
+ console.print("[white]Sources: default (built-in), user (~/.tweek/config.yaml), project (.tweek/config.yaml)[/white]")
2923
3205
 
2924
3206
 
2925
3207
  @config.command("set",
@@ -2982,11 +3264,11 @@ def config_preset(preset_name: str, scope: str):
2982
3264
  console.print(f"[green]✓[/green] Applied [bold]{preset_name}[/bold] preset ({scope} config)")
2983
3265
 
2984
3266
  if preset_name == "paranoid":
2985
- console.print("[dim]All tools require screening, Bash commands always sandboxed[/dim]")
3267
+ console.print("[white]All tools require screening, Bash commands always sandboxed[/white]")
2986
3268
  elif preset_name == "cautious":
2987
- console.print("[dim]Balanced: read-only tools safe, Bash dangerous[/dim]")
3269
+ console.print("[white]Balanced: read-only tools safe, Bash dangerous[/white]")
2988
3270
  elif preset_name == "trusted":
2989
- console.print("[dim]Minimal prompts: only high-risk patterns trigger alerts[/dim]")
3271
+ console.print("[white]Minimal prompts: only high-risk patterns trigger alerts[/white]")
2990
3272
 
2991
3273
 
2992
3274
  @config.command("reset",
@@ -3011,7 +3293,7 @@ def config_reset(skill: str, tool: str, reset_all: bool, scope: str, confirm: bo
3011
3293
 
3012
3294
  if reset_all:
3013
3295
  if not confirm and not click.confirm(f"Reset ALL {scope} configuration?"):
3014
- console.print("[dim]Cancelled[/dim]")
3296
+ console.print("[white]Cancelled[/white]")
3015
3297
  return
3016
3298
  cfg.reset_all(scope=scope)
3017
3299
  console.print(f"[green]✓[/green] Reset all {scope} configuration to defaults")
@@ -3069,7 +3351,7 @@ def config_validate(scope: str, json_out: bool):
3069
3351
  console.print()
3070
3352
  console.print("[bold]Configuration Validation[/bold]")
3071
3353
  console.print("\u2500" * 40)
3072
- console.print(f"[dim]Scope: {scope}[/dim]")
3354
+ console.print(f"[white]Scope: {scope}[/white]")
3073
3355
  console.print()
3074
3356
 
3075
3357
  if not issues:
@@ -3085,11 +3367,11 @@ def config_validate(scope: str, json_out: bool):
3085
3367
  level_styles = {
3086
3368
  "error": "[red]ERROR[/red]",
3087
3369
  "warning": "[yellow]WARN[/yellow] ",
3088
- "info": "[dim]INFO[/dim] ",
3370
+ "info": "[white]INFO[/white] ",
3089
3371
  }
3090
3372
 
3091
3373
  for issue in issues:
3092
- style = level_styles.get(issue.level, "[dim]???[/dim] ")
3374
+ style = level_styles.get(issue.level, "[white]???[/white] ")
3093
3375
  msg = f" {style} {issue.key} \u2192 {issue.message}"
3094
3376
  if issue.suggestion:
3095
3377
  msg += f" {issue.suggestion}"
@@ -3201,7 +3483,7 @@ def config_llm(verbose: bool, validate: bool):
3201
3483
  console.print()
3202
3484
  console.print(" [yellow]Status:[/yellow] Disabled (no provider available)")
3203
3485
  console.print()
3204
- console.print(" [dim]To enable, set one of:[/dim]")
3486
+ console.print(" [white]To enable, set one of:[/white]")
3205
3487
  console.print(" ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY")
3206
3488
  console.print(" Or install Ollama: [cyan]https://ollama.ai[/cyan]")
3207
3489
  console.print()
@@ -3236,8 +3518,8 @@ def config_llm(verbose: bool, validate: bool):
3236
3518
  for m in server.all_models:
3237
3519
  console.print(f" - {m}")
3238
3520
  else:
3239
- console.print(" [dim]No local LLM server detected[/dim]")
3240
- console.print(" [dim]Checked: Ollama (localhost:11434), LM Studio (localhost:1234)[/dim]")
3521
+ console.print(" [white]No local LLM server detected[/white]")
3522
+ console.print(" [white]Checked: Ollama (localhost:11434), LM Studio (localhost:1234)[/white]")
3241
3523
  except Exception as e:
3242
3524
  console.print(f" [yellow]Detection error: {e}[/yellow]")
3243
3525
 
@@ -3275,8 +3557,8 @@ def config_llm(verbose: bool, validate: bool):
3275
3557
  console.print(f" [green]PASSED[/green] ({score:.0%})")
3276
3558
  else:
3277
3559
  console.print(f" [red]FAILED[/red] ({score:.0%}, minimum: 60%)")
3278
- console.print(" [dim]This model may not reliably classify security threats.[/dim]")
3279
- console.print(" [dim]Try a larger model: ollama pull qwen2.5:7b-instruct[/dim]")
3560
+ console.print(" [white]This model may not reliably classify security threats.[/white]")
3561
+ console.print(" [white]Try a larger model: ollama pull qwen2.5:7b-instruct[/white]")
3280
3562
  except Exception as e:
3281
3563
  console.print(f" [red]Validation error: {e}[/red]")
3282
3564
 
@@ -3306,8 +3588,8 @@ def vault_store(skill: str, key: str, value: Optional[str]):
3306
3588
 
3307
3589
  if not VAULT_AVAILABLE:
3308
3590
  console.print("[red]\u2717[/red] Vault not available.")
3309
- console.print(" [dim]Hint: Install keyring support: pip install keyring[/dim]")
3310
- console.print(" [dim]On macOS, keyring uses Keychain. On Linux, install gnome-keyring or kwallet.[/dim]")
3591
+ console.print(" [white]Hint: Install keyring support: pip install keyring[/white]")
3592
+ console.print(" [white]On macOS, keyring uses Keychain. On Linux, install gnome-keyring or kwallet.[/white]")
3311
3593
  return
3312
3594
 
3313
3595
  caps = get_capabilities()
@@ -3323,13 +3605,13 @@ def vault_store(skill: str, key: str, value: Optional[str]):
3323
3605
  vault_instance = get_vault()
3324
3606
  if vault_instance.store(skill, key, value):
3325
3607
  console.print(f"[green]\u2713[/green] Stored {key} for skill '{skill}'")
3326
- console.print(f"[dim]Backend: {caps.vault_backend}[/dim]")
3608
+ console.print(f"[white]Backend: {caps.vault_backend}[/white]")
3327
3609
  else:
3328
3610
  console.print(f"[red]\u2717[/red] Failed to store credential")
3329
- console.print(" [dim]Hint: Check your keyring backend is unlocked and accessible[/dim]")
3611
+ console.print(" [white]Hint: Check your keyring backend is unlocked and accessible[/white]")
3330
3612
  except Exception as e:
3331
3613
  console.print(f"[red]\u2717[/red] Failed to store credential: {e}")
3332
- console.print(" [dim]Hint: Check your keyring backend is unlocked and accessible[/dim]")
3614
+ console.print(" [white]Hint: Check your keyring backend is unlocked and accessible[/white]")
3333
3615
 
3334
3616
 
3335
3617
  @vault.command("get",
@@ -3347,7 +3629,7 @@ def vault_get(skill: str, key: str):
3347
3629
 
3348
3630
  if not VAULT_AVAILABLE:
3349
3631
  console.print("[red]\u2717[/red] Vault not available.")
3350
- console.print(" [dim]Hint: Install keyring support: pip install keyring[/dim]")
3632
+ console.print(" [white]Hint: Install keyring support: pip install keyring[/white]")
3351
3633
  return
3352
3634
 
3353
3635
  vault_instance = get_vault()
@@ -3361,7 +3643,7 @@ def vault_get(skill: str, key: str):
3361
3643
  console.print(value)
3362
3644
  else:
3363
3645
  console.print(f"[red]\u2717[/red] Credential not found: {key} for skill '{skill}'")
3364
- console.print(" [dim]Hint: Store it with: tweek vault store {skill} {key} <value>[/dim]".format(skill=skill, key=key))
3646
+ console.print(" [white]Hint: Store it with: tweek vault store {skill} {key} <value>[/white]".format(skill=skill, key=key))
3365
3647
 
3366
3648
 
3367
3649
  @vault.command("migrate-env",
@@ -3401,7 +3683,7 @@ def vault_migrate_env(dry_run: bool, env_file: str, skill: str):
3401
3683
  successful = sum(1 for _, s in results if s)
3402
3684
  console.print(f"\n[green]✓[/green] {'Would migrate' if dry_run else 'Migrated'} {successful} credentials to skill '{skill}'")
3403
3685
  else:
3404
- console.print("[dim]No credentials found to migrate[/dim]")
3686
+ console.print("[white]No credentials found to migrate[/white]")
3405
3687
 
3406
3688
  except Exception as e:
3407
3689
  console.print(f"[red]✗[/red] Migration failed: {e}")
@@ -3468,16 +3750,16 @@ def license_status():
3468
3750
  console.print(f"[bold]License Tier:[/bold] [{tier_color}]{lic.tier.value.upper()}[/{tier_color}]")
3469
3751
 
3470
3752
  if info:
3471
- console.print(f"[dim]Licensed to: {info.email}[/dim]")
3753
+ console.print(f"[white]Licensed to: {info.email}[/white]")
3472
3754
  if info.expires_at:
3473
3755
  from datetime import datetime
3474
3756
  exp_date = datetime.fromtimestamp(info.expires_at).strftime("%Y-%m-%d")
3475
3757
  if info.is_expired:
3476
3758
  console.print(f"[red]Expired: {exp_date}[/red]")
3477
3759
  else:
3478
- console.print(f"[dim]Expires: {exp_date}[/dim]")
3760
+ console.print(f"[white]Expires: {exp_date}[/white]")
3479
3761
  else:
3480
- console.print("[dim]Expires: Never[/dim]")
3762
+ console.print("[white]Expires: Never[/white]")
3481
3763
  console.print()
3482
3764
 
3483
3765
  # Features table
@@ -3494,7 +3776,7 @@ def license_status():
3494
3776
 
3495
3777
  for feature, required_tier in feature_tiers.items():
3496
3778
  has_it = lic.has_feature(feature)
3497
- status = "[green]✓[/green]" if has_it else "[dim]○[/dim]"
3779
+ status = "[green]✓[/green]" if has_it else "[white]○[/white]"
3498
3780
  tier_display = required_tier.value.upper()
3499
3781
  if required_tier == Tier.PRO:
3500
3782
  tier_display = f"[cyan]{tier_display}[/cyan]"
@@ -3506,7 +3788,7 @@ def license_status():
3506
3788
  if lic.tier == Tier.FREE:
3507
3789
  console.print()
3508
3790
  console.print("[green]All security features are included free and open source.[/green]")
3509
- console.print("[dim]Pro (teams) and Enterprise (compliance) coming soon: gettweek.com[/dim]")
3791
+ console.print("[white]Pro (teams) and Enterprise (compliance) coming soon: gettweek.com[/white]")
3510
3792
 
3511
3793
 
3512
3794
  @license.command("activate",
@@ -3526,7 +3808,7 @@ def license_activate(license_key: str):
3526
3808
  if success:
3527
3809
  console.print(f"[green]✓[/green] {message}")
3528
3810
  console.print()
3529
- console.print("[dim]Run 'tweek license status' to see available features[/dim]")
3811
+ console.print("[white]Run 'tweek license status' to see available features[/white]")
3530
3812
  else:
3531
3813
  console.print(f"[red]✗[/red] {message}")
3532
3814
 
@@ -3546,7 +3828,7 @@ def license_deactivate(confirm: bool):
3546
3828
  if not confirm:
3547
3829
  console.print("[yellow]Deactivate license and revert to FREE tier?[/yellow] ", end="")
3548
3830
  if not click.confirm(""):
3549
- console.print("[dim]Cancelled[/dim]")
3831
+ console.print("[white]Cancelled[/white]")
3550
3832
  return
3551
3833
 
3552
3834
  lic = get_license()
@@ -3624,7 +3906,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3624
3906
  table.add_column("Severity")
3625
3907
  table.add_column("Count", justify="right")
3626
3908
 
3627
- severity_styles = {"critical": "red", "high": "yellow", "medium": "blue", "low": "dim"}
3909
+ severity_styles = {"critical": "red", "high": "yellow", "medium": "blue", "low": "white"}
3628
3910
  for pattern in stat_data['top_patterns']:
3629
3911
  sev = pattern['severity'] or "unknown"
3630
3912
  style = severity_styles.get(sev, "white")
@@ -3661,7 +3943,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3661
3943
  et = EventType(event_type)
3662
3944
  except ValueError:
3663
3945
  console.print(f"[red]Unknown event type: {event_type}[/red]")
3664
- console.print(f"[dim]Valid types: {', '.join(e.value for e in EventType)}[/dim]")
3946
+ console.print(f"[white]Valid types: {', '.join(e.value for e in EventType)}[/white]")
3665
3947
  return
3666
3948
 
3667
3949
  events = logger.get_recent_events(limit=limit, event_type=et, tool_name=tool)
@@ -3672,7 +3954,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3672
3954
  return
3673
3955
 
3674
3956
  table = Table(title=title)
3675
- table.add_column("Time", style="dim")
3957
+ table.add_column("Time", style="white")
3676
3958
  table.add_column("Type", style="cyan")
3677
3959
  table.add_column("Tool", style="green")
3678
3960
  table.add_column("Tier")
@@ -3713,7 +3995,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3713
3995
  )
3714
3996
 
3715
3997
  console.print(table)
3716
- console.print(f"\n[dim]Showing {len(events)} events. Use --limit to see more.[/dim]")
3998
+ console.print(f"\n[white]Showing {len(events)} events. Use --limit to see more.[/white]")
3717
3999
 
3718
4000
 
3719
4001
  @logs.command("export",
@@ -3764,7 +4046,7 @@ def logs_clear(days: int, confirm: bool):
3764
4046
 
3765
4047
  console.print(f"[yellow]{msg}[/yellow] ", end="")
3766
4048
  if not click.confirm(""):
3767
- console.print("[dim]Cancelled[/dim]")
4049
+ console.print("[white]Cancelled[/white]")
3768
4050
  return
3769
4051
 
3770
4052
  logger = get_logger()
@@ -3776,7 +4058,7 @@ def logs_clear(days: int, confirm: bool):
3776
4058
  else:
3777
4059
  console.print(f"[green]Cleared {deleted} event(s)[/green]")
3778
4060
  else:
3779
- console.print("[dim]No events to clear[/dim]")
4061
+ console.print("[white]No events to clear[/white]")
3780
4062
 
3781
4063
 
3782
4064
  @logs.command("bundle",
@@ -3814,11 +4096,11 @@ def logs_bundle(output: str, days: int, no_redact: bool, dry_run: bool):
3814
4096
  size = item.get("size")
3815
4097
  size_str = f" ({size:,} bytes)" if size else ""
3816
4098
  if "not found" in status:
3817
- console.print(f" [dim] SKIP {name} ({status})[/dim]")
4099
+ console.print(f" [white] SKIP {name} ({status})[/white]")
3818
4100
  else:
3819
4101
  console.print(f" [green] ADD {name}{size_str}[/green]")
3820
4102
  console.print()
3821
- console.print("[dim]No files will be collected in dry-run mode.[/dim]")
4103
+ console.print("[white]No files will be collected in dry-run mode.[/white]")
3822
4104
  return
3823
4105
 
3824
4106
  # Determine output path
@@ -3836,9 +4118,9 @@ def logs_bundle(output: str, days: int, no_redact: bool, dry_run: bool):
3836
4118
  result = collector.create_bundle(output_path)
3837
4119
  size = result.stat().st_size
3838
4120
  console.print(f"\n[green]Bundle created: {result}[/green]")
3839
- console.print(f"[dim]Size: {size:,} bytes[/dim]")
4121
+ console.print(f"[white]Size: {size:,} bytes[/white]")
3840
4122
  if not no_redact:
3841
- console.print("[dim]Sensitive data has been redacted.[/dim]")
4123
+ console.print("[white]Sensitive data has been redacted.[/white]")
3842
4124
  console.print(f"\n[bold]Send this file to Tweek support for analysis.[/bold]")
3843
4125
  except Exception as e:
3844
4126
  console.print(f"[red]Failed to create bundle: {e}[/red]")
@@ -3884,8 +4166,8 @@ def proxy_start(port: int, web_port: int, foreground: bool, log_only: bool):
3884
4166
 
3885
4167
  if not PROXY_AVAILABLE:
3886
4168
  console.print("[red]\u2717[/red] Proxy dependencies not installed.")
3887
- console.print(" [dim]Hint: Install with: pip install tweek[proxy][/dim]")
3888
- console.print(" [dim]This adds mitmproxy for HTTP(S) interception.[/dim]")
4169
+ console.print(" [white]Hint: Install with: pip install tweek[proxy][/white]")
4170
+ console.print(" [white]This adds mitmproxy for HTTP(S) interception.[/white]")
3889
4171
  return
3890
4172
 
3891
4173
  from tweek.proxy.server import start_proxy
@@ -3906,7 +4188,7 @@ def proxy_start(port: int, web_port: int, foreground: bool, log_only: bool):
3906
4188
  console.print(f" export HTTPS_PROXY=http://127.0.0.1:{port}")
3907
4189
  console.print(f" export HTTP_PROXY=http://127.0.0.1:{port}")
3908
4190
  console.print()
3909
- console.print("[dim]Or use 'tweek proxy wrap' to create a wrapper script[/dim]")
4191
+ console.print("[white]Or use 'tweek proxy wrap' to create a wrapper script[/white]")
3910
4192
  else:
3911
4193
  console.print(f"[red]✗[/red] {message}")
3912
4194
 
@@ -3951,7 +4233,7 @@ def proxy_trust():
3951
4233
 
3952
4234
  if not PROXY_AVAILABLE:
3953
4235
  console.print("[red]✗[/red] Proxy dependencies not installed.")
3954
- console.print("[dim]Run: pip install tweek\\[proxy][/dim]")
4236
+ console.print("[white]Run: pip install tweek\\[proxy][/white]")
3955
4237
  return
3956
4238
 
3957
4239
  from tweek.proxy.server import install_ca_certificate, get_proxy_info
@@ -3963,11 +4245,11 @@ def proxy_trust():
3963
4245
  console.print("This will install a local CA certificate to enable HTTPS interception.")
3964
4246
  console.print("The certificate is generated on YOUR machine and never transmitted.")
3965
4247
  console.print()
3966
- console.print(f"[dim]Certificate location: {info['ca_cert']}[/dim]")
4248
+ console.print(f"[white]Certificate location: {info['ca_cert']}[/white]")
3967
4249
  console.print()
3968
4250
 
3969
4251
  if not click.confirm("Install certificate? (requires admin password)"):
3970
- console.print("[dim]Cancelled[/dim]")
4252
+ console.print("[white]Cancelled[/white]")
3971
4253
  return
3972
4254
 
3973
4255
  success, message = install_ca_certificate()
@@ -4019,7 +4301,7 @@ def proxy_config(set_enabled, set_disabled, port):
4019
4301
  yaml.dump(config, f, default_flow_style=False)
4020
4302
 
4021
4303
  console.print(f"[green]✓[/green] Proxy mode enabled (port {port})")
4022
- console.print("[dim]Run 'tweek proxy start' to start the proxy[/dim]")
4304
+ console.print("[white]Run 'tweek proxy start' to start the proxy[/white]")
4023
4305
 
4024
4306
  elif set_disabled:
4025
4307
  if "proxy" in config:
@@ -4061,10 +4343,10 @@ def proxy_wrap(app_name: str, command: str, output: str, port: int):
4061
4343
  console.print(f" chmod +x {output_path}")
4062
4344
  console.print(f" ./{output_path.name}")
4063
4345
  console.print()
4064
- console.print("[dim]The script will:[/dim]")
4065
- console.print("[dim] 1. Start Tweek proxy if not running[/dim]")
4066
- console.print("[dim] 2. Set proxy environment variables[/dim]")
4067
- console.print(f"[dim] 3. Run: {command}[/dim]")
4346
+ console.print("[white]The script will:[/white]")
4347
+ console.print("[white] 1. Start Tweek proxy if not running[/white]")
4348
+ console.print("[white] 2. Set proxy environment variables[/white]")
4349
+ console.print(f"[white] 3. Run: {command}[/white]")
4068
4350
 
4069
4351
 
4070
4352
  @proxy.command("setup",
@@ -4139,9 +4421,9 @@ def proxy_setup():
4139
4421
  print_warning("Certificate module not available. Run: tweek proxy trust")
4140
4422
  except Exception as e:
4141
4423
  print_warning(f"Could not set up certificate: {e}")
4142
- console.print(" [dim]You can do this later with: tweek proxy trust[/dim]")
4424
+ console.print(" [white]You can do this later with: tweek proxy trust[/white]")
4143
4425
  else:
4144
- console.print(" [dim]Skipped. Run 'tweek proxy trust' later.[/dim]")
4426
+ console.print(" [white]Skipped. Run 'tweek proxy trust' later.[/white]")
4145
4427
  console.print()
4146
4428
 
4147
4429
  # Step 3: Shell environment
@@ -4165,13 +4447,13 @@ def proxy_setup():
4165
4447
  f.write(f"export HTTP_PROXY=http://127.0.0.1:{port}\n")
4166
4448
  f.write(f"export HTTPS_PROXY=http://127.0.0.1:{port}\n")
4167
4449
  print_success(f"Added to {shell_rc}")
4168
- console.print(f" [dim]Restart your shell or run: source {shell_rc}[/dim]")
4450
+ console.print(f" [white]Restart your shell or run: source {shell_rc}[/white]")
4169
4451
  except Exception as e:
4170
4452
  print_warning(f"Could not write to {shell_rc}: {e}")
4171
4453
  else:
4172
- console.print(" [dim]Skipped. Set HTTP_PROXY and HTTPS_PROXY manually.[/dim]")
4454
+ console.print(" [white]Skipped. Set HTTP_PROXY and HTTPS_PROXY manually.[/white]")
4173
4455
  else:
4174
- console.print(" [dim]Could not detect shell config file.[/dim]")
4456
+ console.print(" [white]Could not detect shell config file.[/white]")
4175
4457
  console.print(f" Add these to your shell profile:")
4176
4458
  console.print(f" export HTTP_PROXY=http://127.0.0.1:{port}")
4177
4459
  console.print(f" export HTTPS_PROXY=http://127.0.0.1:{port}")
@@ -4265,7 +4547,7 @@ def plugins_list(category: str, show_all: bool):
4265
4547
  license_style = "green" if license_tier == LicenseTier.FREE else "cyan"
4266
4548
 
4267
4549
  source_str = info.source.value if hasattr(info, 'source') else "builtin"
4268
- source_style = "blue" if source_str == "git" else "dim"
4550
+ source_style = "blue" if source_str == "git" else "white"
4269
4551
 
4270
4552
  table.add_row(
4271
4553
  info.name,
@@ -4279,6 +4561,18 @@ def plugins_list(category: str, show_all: bool):
4279
4561
  console.print(table)
4280
4562
  console.print()
4281
4563
 
4564
+ # Summary line across all categories
4565
+ total_count = 0
4566
+ enabled_count = 0
4567
+ for cat in list(PluginCategory):
4568
+ for info in registry.list_plugins(cat):
4569
+ total_count += 1
4570
+ if info.enabled:
4571
+ enabled_count += 1
4572
+ disabled_count = total_count - enabled_count
4573
+ console.print(f"Plugins: {total_count} registered, {enabled_count} enabled, {disabled_count} disabled")
4574
+ console.print()
4575
+
4282
4576
  except ImportError as e:
4283
4577
  console.print(f"[red]Plugin system not available: {e}[/red]")
4284
4578
 
@@ -4335,7 +4629,7 @@ def plugins_info(plugin_name: str, category: str):
4335
4629
  plugin_cfg = cfg.get_plugin_config(found_cat, plugin_name)
4336
4630
 
4337
4631
  console.print(f"\n[bold]{found_info.name}[/bold] ({found_cat})")
4338
- console.print(f"[dim]{found_info.metadata.description}[/dim]")
4632
+ console.print(f"[white]{found_info.metadata.description}[/white]")
4339
4633
  console.print()
4340
4634
 
4341
4635
  table = Table(show_header=False)
@@ -4507,7 +4801,7 @@ def plugins_scan(content: str, direction: str, plugin: str):
4507
4801
 
4508
4802
  if not plugins_to_use:
4509
4803
  console.print("[yellow]No compliance plugins enabled.[/yellow]")
4510
- console.print("[dim]Enable plugins with: tweek plugins enable <name> -c compliance[/dim]")
4804
+ console.print("[white]Enable plugins with: tweek plugins enable <name> -c compliance[/white]")
4511
4805
  return
4512
4806
 
4513
4807
  for p in plugins_to_use:
@@ -4521,12 +4815,12 @@ def plugins_scan(content: str, direction: str, plugin: str):
4521
4815
  "critical": "red bold",
4522
4816
  "high": "red",
4523
4817
  "medium": "yellow",
4524
- "low": "dim",
4818
+ "low": "white",
4525
4819
  }
4526
4820
  style = severity_styles.get(finding.severity.value, "white")
4527
4821
 
4528
4822
  console.print(f" [{style}]{finding.severity.value.upper()}[/{style}] {finding.pattern_name}")
4529
- console.print(f" [dim]Matched: {finding.matched_text[:60]}{'...' if len(finding.matched_text) > 60 else ''}[/dim]")
4823
+ console.print(f" [white]Matched: {finding.matched_text[:60]}{'...' if len(finding.matched_text) > 60 else ''}[/white]")
4530
4824
  if finding.description:
4531
4825
  console.print(f" {finding.description}")
4532
4826
 
@@ -4600,11 +4894,11 @@ def plugins_install(name: str, version: str, from_lockfile: bool, no_verify: boo
4600
4894
  console.print(f"[green]\u2713[/green] {msg}")
4601
4895
  else:
4602
4896
  console.print(f"[red]\u2717[/red] {msg}")
4603
- console.print(f" [dim]Hint: Check network connectivity or try: tweek plugins registry --refresh[/dim]")
4897
+ console.print(f" [white]Hint: Check network connectivity or try: tweek plugins registry --refresh[/white]")
4604
4898
 
4605
4899
  except Exception as e:
4606
4900
  console.print(f"[red]Error: {e}[/red]")
4607
- console.print(f" [dim]Hint: Check network connectivity and try again[/dim]")
4901
+ console.print(f" [white]Hint: Check network connectivity and try again[/white]")
4608
4902
 
4609
4903
 
4610
4904
  @plugins.command("update",
@@ -4972,87 +5266,6 @@ def serve():
4972
5266
  console.print(f"[red]MCP server error: {e}[/red]")
4973
5267
 
4974
5268
 
4975
- @mcp.command(
4976
- epilog="""\b
4977
- Examples:
4978
- tweek mcp install claude-desktop Configure Claude Desktop integration
4979
- tweek mcp install chatgpt Set up ChatGPT Desktop integration
4980
- tweek mcp install gemini Configure Gemini CLI integration
4981
- """
4982
- )
4983
- @click.argument("client", type=click.Choice(["claude-desktop", "chatgpt", "gemini"]))
4984
- def install(client):
4985
- """Install Tweek as MCP server for a desktop client.
4986
-
4987
- Supported clients:
4988
- claude-desktop - Auto-configures Claude Desktop
4989
- chatgpt - Provides Developer Mode setup instructions
4990
- gemini - Auto-configures Gemini CLI settings
4991
- """
4992
- try:
4993
- from tweek.mcp.clients import get_client
4994
-
4995
- handler = get_client(client)
4996
- result = handler.install()
4997
-
4998
- if result.get("success"):
4999
- console.print(f"[green]✅ {result.get('message', 'Installed successfully')}[/green]")
5000
-
5001
- if result.get("config_path"):
5002
- console.print(f" Config: {result['config_path']}")
5003
-
5004
- if result.get("backup"):
5005
- console.print(f" Backup: {result['backup']}")
5006
-
5007
- # Show instructions for manual setup clients
5008
- if result.get("instructions"):
5009
- console.print()
5010
- for line in result["instructions"]:
5011
- console.print(f" {line}")
5012
- else:
5013
- console.print(f"[red]❌ {result.get('error', 'Installation failed')}[/red]")
5014
-
5015
- except Exception as e:
5016
- console.print(f"[red]Error: {e}[/red]")
5017
-
5018
-
5019
- @mcp.command(
5020
- epilog="""\b
5021
- Examples:
5022
- tweek mcp uninstall claude-desktop Remove from Claude Desktop
5023
- tweek mcp uninstall chatgpt Remove from ChatGPT Desktop
5024
- tweek mcp uninstall gemini Remove from Gemini CLI
5025
- """
5026
- )
5027
- @click.argument("client", type=click.Choice(["claude-desktop", "chatgpt", "gemini"]))
5028
- def uninstall(client):
5029
- """Remove Tweek MCP server from a desktop client.
5030
-
5031
- Supported clients: claude-desktop, chatgpt, gemini
5032
- """
5033
- try:
5034
- from tweek.mcp.clients import get_client
5035
-
5036
- handler = get_client(client)
5037
- result = handler.uninstall()
5038
-
5039
- if result.get("success"):
5040
- console.print(f"[green]✅ {result.get('message', 'Uninstalled successfully')}[/green]")
5041
-
5042
- if result.get("backup"):
5043
- console.print(f" Backup: {result['backup']}")
5044
-
5045
- if result.get("instructions"):
5046
- console.print()
5047
- for line in result["instructions"]:
5048
- console.print(f" {line}")
5049
- else:
5050
- console.print(f"[red]❌ {result.get('error', 'Uninstallation failed')}[/red]")
5051
-
5052
- except Exception as e:
5053
- console.print(f"[red]Error: {e}[/red]")
5054
-
5055
-
5056
5269
  # =============================================================================
5057
5270
  # MCP PROXY COMMANDS
5058
5271
  # =============================================================================
@@ -5208,13 +5421,13 @@ def chamber_list():
5208
5421
  items = chamber.list_chamber()
5209
5422
 
5210
5423
  if not items:
5211
- console.print("[dim]Chamber is empty.[/dim]")
5424
+ console.print("[white]Chamber is empty.[/white]")
5212
5425
  return
5213
5426
 
5214
5427
  table = Table(title="Isolation Chamber")
5215
5428
  table.add_column("Name", style="cyan")
5216
5429
  table.add_column("Has SKILL.md", style="green")
5217
- table.add_column("Path", style="dim")
5430
+ table.add_column("Path", style="white")
5218
5431
 
5219
5432
  for item in items:
5220
5433
  has_md = "Yes" if item["has_skill_md"] else "[red]No[/red]"
@@ -5309,7 +5522,7 @@ def jail_list():
5309
5522
  items = chamber.list_jail()
5310
5523
 
5311
5524
  if not items:
5312
- console.print("[dim]Jail is empty.[/dim]")
5525
+ console.print("[white]Jail is empty.[/white]")
5313
5526
  return
5314
5527
 
5315
5528
  table = Table(title="Skill Jail")
@@ -5381,7 +5594,7 @@ def skills_report(name: str):
5381
5594
  report_data = chamber.get_report(name)
5382
5595
 
5383
5596
  if not report_data:
5384
- console.print(f"[dim]No report found for '{name}'.[/dim]")
5597
+ console.print(f"[white]No report found for '{name}'.[/white]")
5385
5598
  return
5386
5599
 
5387
5600
  console.print(Panel(
@@ -5503,7 +5716,7 @@ def sandbox_status():
5503
5716
  else:
5504
5717
  console.print(f"[bold]Project:[/bold] {project_dir}")
5505
5718
  console.print(f"[bold]Layer:[/bold] 0-1 (no project isolation)")
5506
- console.print("[dim]Run 'tweek sandbox init' to enable project isolation.[/dim]")
5719
+ console.print("[white]Run 'tweek sandbox init' to enable project isolation.[/white]")
5507
5720
 
5508
5721
 
5509
5722
  @sandbox.command("init")
@@ -5577,7 +5790,7 @@ def sandbox_list():
5577
5790
  projects = registry.list_projects()
5578
5791
 
5579
5792
  if not projects:
5580
- console.print("[dim]No projects registered. Run 'tweek sandbox init' in a project.[/dim]")
5793
+ console.print("[white]No projects registered. Run 'tweek sandbox init' in a project.[/white]")
5581
5794
  return
5582
5795
 
5583
5796
  table = Table(title="Registered Projects")
@@ -5649,12 +5862,12 @@ def sandbox_logs(show_global: bool, limit: int):
5649
5862
 
5650
5863
  events = logger.get_recent_events(limit=limit)
5651
5864
  if not events:
5652
- console.print("[dim]No events found.[/dim]")
5865
+ console.print("[white]No events found.[/white]")
5653
5866
  return
5654
5867
 
5655
5868
  from rich.table import Table
5656
5869
  table = Table()
5657
- table.add_column("Time", style="dim")
5870
+ table.add_column("Time", style="white")
5658
5871
  table.add_column("Type")
5659
5872
  table.add_column("Tool")
5660
5873
  table.add_column("Decision", style="green")
@@ -5734,7 +5947,7 @@ def sandbox_verify():
5734
5947
  checks_passed += 1
5735
5948
  else:
5736
5949
  console.print(" Sandbox initialized: [red]NO[/red]")
5737
- console.print(" [dim]Run 'tweek sandbox init' to enable.[/dim]")
5950
+ console.print(" [white]Run 'tweek sandbox init' to enable.[/white]")
5738
5951
 
5739
5952
  # Check 3: Layer
5740
5953
  checks_total += 1
@@ -5755,7 +5968,7 @@ def sandbox_verify():
5755
5968
  elif sandbox:
5756
5969
  console.print(" Project security.db: [yellow]NOT FOUND[/yellow]")
5757
5970
  else:
5758
- console.print(" Project security.db: [dim]N/A (sandbox inactive)[/dim]")
5971
+ console.print(" Project security.db: [white]N/A (sandbox inactive)[/white]")
5759
5972
 
5760
5973
  # Check 5: .gitignore
5761
5974
  checks_total += 1
@@ -5785,7 +5998,7 @@ def docker_init():
5785
5998
  bridge = DockerBridge()
5786
5999
  if not bridge.is_docker_available():
5787
6000
  console.print("[red]Docker is not installed or not running.[/red]")
5788
- console.print("[dim]Install Docker Desktop from https://www.docker.com/products/docker-desktop/[/dim]")
6001
+ console.print("[white]Install Docker Desktop from https://www.docker.com/products/docker-desktop/[/white]")
5789
6002
  raise SystemExit(1)
5790
6003
 
5791
6004
  from tweek.sandbox.project import _detect_project_dir
@@ -5796,7 +6009,7 @@ def docker_init():
5796
6009
 
5797
6010
  compose_path = bridge.init(project_dir)
5798
6011
  console.print(f"[green]Docker Sandbox config generated: {compose_path}[/green]")
5799
- console.print("[dim]Run 'tweek sandbox docker run' to start the container.[/dim]")
6012
+ console.print("[white]Run 'tweek sandbox docker run' to start the container.[/white]")
5800
6013
 
5801
6014
 
5802
6015
  @sandbox_docker.command("run")
@@ -5833,7 +6046,7 @@ def docker_status():
5833
6046
  compose = project_dir / ".tweek" / "docker-compose.yaml"
5834
6047
  console.print(f"[bold]Docker config:[/bold] {'exists' if compose.exists() else 'not generated'}")
5835
6048
  else:
5836
- console.print("[dim]Not in a project directory.[/dim]")
6049
+ console.print("[white]Not in a project directory.[/white]")
5837
6050
 
5838
6051
 
5839
6052
  # =========================================================================
@@ -5894,7 +6107,7 @@ def override_create(pattern: str, mode: str, duration_minutes: Optional[int], re
5894
6107
  if reason:
5895
6108
  console.print(f" Reason: {reason}")
5896
6109
  console.print()
5897
- console.print("[dim]Next time this pattern triggers, you'll see an 'ask' prompt instead of a hard block.[/dim]")
6110
+ console.print("[white]Next time this pattern triggers, you'll see an 'ask' prompt instead of a hard block.[/white]")
5898
6111
 
5899
6112
 
5900
6113
  @override_group.command("list")
@@ -5907,7 +6120,7 @@ def override_list():
5907
6120
  active_patterns = {o["pattern"] for o in active}
5908
6121
 
5909
6122
  if not all_overrides:
5910
- console.print("[dim]No break-glass overrides found.[/dim]")
6123
+ console.print("[white]No break-glass overrides found.[/white]")
5911
6124
  return
5912
6125
 
5913
6126
  table = Table(title="Break-Glass Overrides")
@@ -5921,9 +6134,9 @@ def override_list():
5921
6134
  if o["pattern"] in active_patterns and not o.get("used"):
5922
6135
  status = "[green]active[/green]"
5923
6136
  elif o.get("used"):
5924
- status = "[dim]consumed[/dim]"
6137
+ status = "[white]consumed[/white]"
5925
6138
  else:
5926
- status = "[dim]expired[/dim]"
6139
+ status = "[white]expired[/white]"
5927
6140
 
5928
6141
  table.add_row(
5929
6142
  o["pattern"],
@@ -6012,7 +6225,7 @@ def feedback_stats(above_threshold: bool):
6012
6225
 
6013
6226
  stats = get_stats()
6014
6227
  if not stats:
6015
- console.print("[dim]No feedback data recorded yet.[/dim]")
6228
+ console.print("[white]No feedback data recorded yet.[/white]")
6016
6229
  return
6017
6230
 
6018
6231
  table = Table(title="Pattern FP Statistics")
@@ -6053,7 +6266,7 @@ def feedback_reset(pattern_name: str):
6053
6266
  if result.get("was_demoted"):
6054
6267
  console.print(f" Restored severity: {result.get('original_severity')}")
6055
6268
  else:
6056
- console.print(f"[dim]No feedback data found for '{pattern_name}'.[/dim]")
6269
+ console.print(f"[white]No feedback data found for '{pattern_name}'.[/white]")
6057
6270
 
6058
6271
 
6059
6272
  # =========================================================================
@@ -6094,7 +6307,7 @@ def memory_status():
6094
6307
  if last_decay:
6095
6308
  console.print(f" Last decay: {last_decay}")
6096
6309
  else:
6097
- console.print(" Last decay: [dim]never[/dim]")
6310
+ console.print(" Last decay: [white]never[/white]")
6098
6311
 
6099
6312
  db_size = stats.get("db_size_bytes", 0)
6100
6313
  if db_size > 1024 * 1024:
@@ -6118,7 +6331,7 @@ def memory_patterns(min_decisions: int, sort_by: str):
6118
6331
  patterns = store.get_pattern_stats(min_decisions=min_decisions, sort_by=sort_by)
6119
6332
 
6120
6333
  if not patterns:
6121
- console.print("[dim]No pattern decision data recorded yet.[/dim]")
6334
+ console.print("[white]No pattern decision data recorded yet.[/white]")
6122
6335
  return
6123
6336
 
6124
6337
  table = Table(title="Pattern Decision History")
@@ -6135,7 +6348,7 @@ def memory_patterns(min_decisions: int, sort_by: str):
6135
6348
  ratio_style = "green" if ratio >= 0.9 else ("yellow" if ratio >= 0.5 else "red")
6136
6349
  table.add_row(
6137
6350
  p.get("pattern_name", "?"),
6138
- p.get("path_prefix") or "[dim]-[/dim]",
6351
+ p.get("path_prefix") or "[white]-[/white]",
6139
6352
  str(p.get("total_decisions", 0)),
6140
6353
  f"{p.get('weighted_approvals', 0):.1f}",
6141
6354
  f"{p.get('weighted_denials', 0):.1f}",
@@ -6156,7 +6369,7 @@ def memory_sources(suspicious: bool):
6156
6369
  sources = store.get_all_sources(suspicious_only=suspicious)
6157
6370
 
6158
6371
  if not sources:
6159
- console.print("[dim]No source trust data recorded yet.[/dim]")
6372
+ console.print("[white]No source trust data recorded yet.[/white]")
6160
6373
  return
6161
6374
 
6162
6375
  table = Table(title="Source Trust Scores")
@@ -6191,7 +6404,7 @@ def memory_suggestions(show_all: bool):
6191
6404
  suggestions = store.get_whitelist_suggestions(pending_only=not show_all)
6192
6405
 
6193
6406
  if not suggestions:
6194
- console.print("[dim]No whitelist suggestions available.[/dim]")
6407
+ console.print("[white]No whitelist suggestions available.[/white]")
6195
6408
  return
6196
6409
 
6197
6410
  table = Table(title="Learned Whitelist Suggestions")
@@ -6209,8 +6422,8 @@ def memory_suggestions(show_all: bool):
6209
6422
  table.add_row(
6210
6423
  str(s.id),
6211
6424
  s.pattern_name,
6212
- s.tool_name or "[dim]-[/dim]",
6213
- s.path_prefix or "[dim]-[/dim]",
6425
+ s.tool_name or "[white]-[/white]",
6426
+ s.path_prefix or "[white]-[/white]",
6214
6427
  str(s.approval_count),
6215
6428
  str(s.denial_count),
6216
6429
  f"{s.confidence:.0%}",
@@ -6229,7 +6442,7 @@ def memory_accept(suggestion_id: int):
6229
6442
  store = get_memory_store()
6230
6443
  if store.review_whitelist_suggestion(suggestion_id, accepted=True):
6231
6444
  console.print(f"[bold green]Accepted[/bold green] suggestion #{suggestion_id}")
6232
- console.print(" [dim]Note: To apply to overrides.yaml, manually add the whitelist rule.[/dim]")
6445
+ console.print(" [white]Note: To apply to overrides.yaml, manually add the whitelist rule.[/white]")
6233
6446
  else:
6234
6447
  console.print(f"[red]Suggestion #{suggestion_id} not found.[/red]")
6235
6448
 
@@ -6260,7 +6473,7 @@ def memory_baseline(project_hash: Optional[str]):
6260
6473
  baselines = store.get_workflow_baseline(project_hash)
6261
6474
 
6262
6475
  if not baselines:
6263
- console.print("[dim]No workflow baseline data for this project.[/dim]")
6476
+ console.print("[white]No workflow baseline data for this project.[/white]")
6264
6477
  return
6265
6478
 
6266
6479
  table = Table(title=f"Workflow Baseline (project: {project_hash[:8]}...)")
@@ -6276,7 +6489,7 @@ def memory_baseline(project_hash: Optional[str]):
6276
6489
  pct_style = "green" if denial_pct < 0.1 else ("yellow" if denial_pct < 0.3 else "red")
6277
6490
  table.add_row(
6278
6491
  b.tool_name,
6279
- str(b.hour_of_day) if b.hour_of_day is not None else "[dim]-[/dim]",
6492
+ str(b.hour_of_day) if b.hour_of_day is not None else "[white]-[/white]",
6280
6493
  str(b.invocation_count),
6281
6494
  str(b.denied_count),
6282
6495
  f"[{pct_style}]{denial_pct:.0%}[/{pct_style}]",
@@ -6295,7 +6508,7 @@ def memory_audit(limit: int):
6295
6508
  entries = store.get_audit_log(limit=limit)
6296
6509
 
6297
6510
  if not entries:
6298
- console.print("[dim]No audit entries.[/dim]")
6511
+ console.print("[white]No audit entries.[/white]")
6299
6512
  return
6300
6513
 
6301
6514
  table = Table(title=f"Memory Audit Log (last {limit})")
@@ -6337,7 +6550,7 @@ def memory_clear(table_name: Optional[str], confirm: bool):
6337
6550
  if not confirm:
6338
6551
  target = table_name or "ALL"
6339
6552
  if not click.confirm(f"Clear {target} memory data? This cannot be undone"):
6340
- console.print("[dim]Cancelled.[/dim]")
6553
+ console.print("[white]Cancelled.[/white]")
6341
6554
  return
6342
6555
 
6343
6556
  store = get_memory_store()