tweek 0.2.1__py3-none-any.whl → 0.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tweek/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,38 @@ 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 unprotect
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
1222
1188
  """
1223
1189
  )
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,
1227
- help="Remove ALL Tweek data: hooks, skills, config, patterns, logs, MCP integrations")
1190
+ @click.argument("tool", required=False, type=click.Choice(
1191
+ ["claude-code", "openclaw", "claude-desktop", "chatgpt", "gemini"]))
1192
+ @click.option("--global", "unprotect_global", is_flag=True, default=False,
1193
+ help="Remove from ~/.claude/ (global installation)")
1228
1194
  @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.
1195
+ def unprotect(tool: str, unprotect_global: bool, confirm: bool):
1196
+ """Remove Tweek protection from an AI tool.
1231
1197
 
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.
1198
+ This removes hooks and MCP configuration for a specific tool
1199
+ but keeps Tweek installed on your system. Use `tweek uninstall`
1200
+ to fully remove Tweek.
1201
+
1202
+ When run without arguments, launches an interactive wizard
1203
+ that walks through each protected tool asking if you want
1204
+ to remove protection.
1235
1205
 
1236
1206
  This command can only be run from an interactive terminal.
1237
1207
  AI agents are blocked from running it.
@@ -1243,66 +1213,118 @@ def uninstall(uninstall_global: bool, everything: bool, confirm: bool):
1243
1213
  # This is Layer 2 of protection (Layer 1 is the PreToolUse hook)
1244
1214
  # ─────────────────────────────────────────────────────────────
1245
1215
  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]")
1216
+ console.print("[red]ERROR: tweek unprotect must be run from an interactive terminal.[/red]")
1217
+ console.print("[white]This command cannot be run by AI agents or automated scripts.[/white]")
1218
+ console.print("[white]Open a terminal and run the command directly.[/white]")
1249
1219
  raise SystemExit(1)
1250
1220
 
1221
+ # No tool: run interactive wizard
1222
+ if not tool:
1223
+ _run_unprotect_wizard()
1224
+ return
1225
+
1251
1226
  console.print(TWEEK_BANNER, style="cyan")
1252
1227
 
1253
1228
  tweek_dir = Path("~/.tweek").expanduser()
1254
1229
  global_target = Path("~/.claude").expanduser()
1255
1230
  project_target = Path.cwd() / ".claude"
1256
1231
 
1257
- if everything:
1258
- _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
1232
+ if tool == "claude-code":
1233
+ if unprotect_global:
1234
+ _uninstall_scope(global_target, tweek_dir, confirm, scope_label="global")
1235
+ else:
1236
+ _uninstall_scope(project_target, tweek_dir, confirm, scope_label="project")
1237
+ return
1275
1238
 
1276
- console.print("[bold]What would you like to remove?[/bold]\n")
1239
+ if tool in ("claude-desktop", "chatgpt", "gemini"):
1240
+ try:
1241
+ from tweek.mcp.clients import get_client
1242
+ handler = get_client(tool)
1243
+ result = handler.uninstall()
1244
+ if result.get("success"):
1245
+ console.print(f"[green]{result.get('message', 'Uninstalled successfully')}[/green]")
1246
+ if result.get("backup"):
1247
+ console.print(f" Backup: {result['backup']}")
1248
+ if result.get("instructions"):
1249
+ console.print()
1250
+ for line in result["instructions"]:
1251
+ console.print(f" {line}")
1252
+ else:
1253
+ console.print(f"[red]{result.get('error', 'Uninstallation failed')}[/red]")
1254
+ except Exception as e:
1255
+ console.print(f"[red]Error: {e}[/red]")
1256
+ return
1277
1257
 
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"))
1258
+ if tool == "openclaw":
1259
+ console.print("[yellow]OpenClaw unprotect: removing Tweek proxy configuration...[/yellow]")
1260
+ # TODO: implement openclaw unprotect
1261
+ console.print("[white]Manual step: remove tweek plugin from openclaw.json[/white]")
1262
+ return
1285
1263
 
1286
- for i, (_, label) in enumerate(options, 1):
1287
- console.print(f" [bold]{i}.[/bold] {label}")
1288
1264
 
1289
- console.print()
1290
- choice = click.prompt("Select", type=click.IntRange(1, len(options)), default=len(options))
1265
+ @main.command(
1266
+ epilog="""\b
1267
+ Examples:
1268
+ tweek uninstall Interactive full removal
1269
+ tweek uninstall --all Remove ALL Tweek data system-wide
1270
+ tweek uninstall --all --confirm Remove everything without prompts
1271
+ """
1272
+ )
1273
+ @click.option("--all", "remove_all", is_flag=True, default=False,
1274
+ help="Remove ALL Tweek data: hooks, skills, config, patterns, logs, MCP integrations")
1275
+ @click.option("--confirm", is_flag=True, help="Skip confirmation prompts")
1276
+ def uninstall(remove_all: bool, confirm: bool):
1277
+ """Fully remove Tweek from your system.
1291
1278
 
1292
- selected = options[choice - 1][0]
1293
- console.print()
1279
+ Removes all hooks, skills, configuration, data, and optionally
1280
+ the Tweek package itself. For removing protection from a single
1281
+ tool without uninstalling, use `tweek unprotect` instead.
1294
1282
 
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)
1283
+ This command can only be run from an interactive terminal.
1284
+ AI agents are blocked from running it.
1285
+ """
1286
+ # ─────────────────────────────────────────────────────────────
1287
+ # HUMAN-ONLY GATE: Block non-interactive execution
1288
+ # ─────────────────────────────────────────────────────────────
1289
+ if not sys.stdin.isatty():
1290
+ console.print("[red]ERROR: tweek uninstall must be run from an interactive terminal.[/red]")
1291
+ console.print("[white]This command cannot be run by AI agents or automated scripts.[/white]")
1292
+ console.print("[white]Open a terminal and run the command directly.[/white]")
1293
+ raise SystemExit(1)
1301
1294
 
1302
- # Always show how to remove the CLI binary itself
1295
+ console.print(TWEEK_BANNER, style="cyan")
1296
+
1297
+ tweek_dir = Path("~/.tweek").expanduser()
1298
+ global_target = Path("~/.claude").expanduser()
1299
+ project_target = Path.cwd() / ".claude"
1300
+
1301
+ if not remove_all:
1302
+ # Interactive: ask what to remove
1303
+ console.print("[bold]What would you like to remove?[/bold]")
1304
+ console.print()
1305
+ console.print(" [bold]1.[/bold] Everything (all hooks, data, config, and package)")
1306
+ console.print(" [bold]2.[/bold] Cancel")
1307
+ console.print()
1308
+ choice = click.prompt("Select", type=click.IntRange(1, 2), default=2)
1309
+ if choice == 2:
1310
+ console.print("[white]Cancelled[/white]")
1311
+ return
1312
+ console.print()
1313
+
1314
+ _uninstall_everything(global_target, project_target, tweek_dir, confirm)
1303
1315
  _show_package_removal_hint()
1304
1316
 
1305
1317
 
1318
+ @main.command()
1319
+ def status():
1320
+ """Show Tweek protection status dashboard.
1321
+
1322
+ Scans for all supported AI tools and displays which are
1323
+ detected, which are protected by Tweek, and configuration details.
1324
+ """
1325
+ _show_protection_status()
1326
+
1327
+
1306
1328
  # ─────────────────────────────────────────────────────────────
1307
1329
  # Uninstall Helpers
1308
1330
  # ─────────────────────────────────────────────────────────────
@@ -1360,9 +1382,9 @@ def _show_package_removal_hint():
1360
1382
  console.print("[bold yellow]The tweek CLI binary is still installed on your system.[/bold yellow]")
1361
1383
 
1362
1384
  if len(pkg_cmds) > 1:
1363
- console.print(f"[dim]Found {len(pkg_cmds)} installations:[/dim]")
1385
+ console.print(f"[white]Found {len(pkg_cmds)} installations:[/white]")
1364
1386
  for cmd in pkg_cmds:
1365
- console.print(f" [dim]• {cmd}[/dim]")
1387
+ console.print(f" [white]• {cmd}[/white]")
1366
1388
 
1367
1389
  console.print()
1368
1390
  label = " + ".join(f"[bold]{cmd}[/bold]" for cmd in pkg_cmds)
@@ -1383,10 +1405,10 @@ def _show_package_removal_hint():
1383
1405
  console.print(f"[green]✓[/green] Removed ({pkg_cmd})")
1384
1406
  else:
1385
1407
  console.print(f"[red]✗[/red] Failed: {result.stderr.strip()}")
1386
- console.print(f" [dim]Run manually: {pkg_cmd}[/dim]")
1408
+ console.print(f" [white]Run manually: {pkg_cmd}[/white]")
1387
1409
  except (subprocess.TimeoutExpired, FileNotFoundError, OSError) as e:
1388
1410
  console.print(f"[red]✗[/red] Could not run: {e}")
1389
- console.print(f" [dim]Run manually: {pkg_cmd}[/dim]")
1411
+ console.print(f" [white]Run manually: {pkg_cmd}[/white]")
1390
1412
 
1391
1413
 
1392
1414
  def _has_tweek_at(target: Path) -> bool:
@@ -1620,18 +1642,18 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1620
1642
  console.print()
1621
1643
  console.print("[bold]The following will be removed:[/bold]")
1622
1644
  if has_hooks:
1623
- console.print(" [dim]•[/dim] PreToolUse and PostToolUse hooks from settings.json")
1645
+ console.print(" [white]•[/white] PreToolUse and PostToolUse hooks from settings.json")
1624
1646
  if has_skills:
1625
- console.print(" [dim]•[/dim] Tweek skill directory (skills/tweek/)")
1647
+ console.print(" [white]•[/white] Tweek skill directory (skills/tweek/)")
1626
1648
  if has_backup:
1627
- console.print(" [dim]•[/dim] Backup file (settings.json.tweek-backup)")
1628
- console.print(" [dim]•[/dim] Project whitelist entries from overrides")
1649
+ console.print(" [white]•[/white] Backup file (settings.json.tweek-backup)")
1650
+ console.print(" [white]•[/white] Project whitelist entries from overrides")
1629
1651
  console.print()
1630
1652
 
1631
1653
  if not confirm:
1632
1654
  console.print(f"[yellow]Remove Tweek from this {scope_label}?[/yellow] ", end="")
1633
1655
  if not click.confirm(""):
1634
- console.print("[dim]Cancelled[/dim]")
1656
+ console.print("[white]Cancelled[/white]")
1635
1657
  return
1636
1658
 
1637
1659
  console.print()
@@ -1647,27 +1669,27 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1647
1669
  if _remove_skill_directory(target):
1648
1670
  console.print(f" [green]✓[/green] Removed Tweek skill directory (skills/tweek/)")
1649
1671
  else:
1650
- console.print(f" [dim]-[/dim] Skipped: Tweek skill directory not found")
1672
+ console.print(f" [white]-[/white] Skipped: Tweek skill directory not found")
1651
1673
 
1652
1674
  # 3. Remove backup file
1653
1675
  if _remove_backup_file(target):
1654
1676
  console.print(f" [green]✓[/green] Removed backup file (settings.json.tweek-backup)")
1655
1677
  else:
1656
- console.print(f" [dim]-[/dim] Skipped: no backup file found")
1678
+ console.print(f" [white]-[/white] Skipped: no backup file found")
1657
1679
 
1658
1680
  # 4. Remove whitelist entries
1659
1681
  wl_count = _remove_whitelist_entries(target, tweek_dir)
1660
1682
  if wl_count > 0:
1661
1683
  console.print(f" [green]✓[/green] Removed {wl_count} whitelist entry(s) from overrides")
1662
1684
  else:
1663
- console.print(f" [dim]-[/dim] Skipped: no whitelist entries found for this {scope_label}")
1685
+ console.print(f" [white]-[/white] Skipped: no whitelist entries found for this {scope_label}")
1664
1686
 
1665
1687
  console.print()
1666
1688
  console.print(f"[green]Uninstall complete.[/green] Tweek is no longer active for this {scope_label}.")
1667
1689
  if scope_label == "project":
1668
- console.print("[dim]Global installation (~/.claude/) was not affected.[/dim]")
1690
+ console.print("[white]Global installation (~/.claude/) was not affected.[/white]")
1669
1691
  else:
1670
- console.print("[dim]Project installations were not affected.[/dim]")
1692
+ console.print("[white]Project installations were not affected.[/white]")
1671
1693
 
1672
1694
  # Offer to remove data directory
1673
1695
  if tweek_dir.exists() and not confirm:
@@ -1680,7 +1702,7 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1680
1702
 
1681
1703
  console.print()
1682
1704
  console.print("[yellow]Also remove Tweek data directory (~/.tweek/)?[/yellow]")
1683
- console.print("[dim]This contains config, patterns, security logs, and overrides.[/dim]")
1705
+ console.print("[white]This contains config, patterns, security logs, and overrides.[/white]")
1684
1706
  if other_has_tweek:
1685
1707
  console.print(f"[bold red]Warning:[/bold red] Tweek is still installed at {other_label} scope ({other_target}).")
1686
1708
  console.print(f" Removing ~/.tweek/ will affect that installation (no config, patterns, or logs).")
@@ -1695,9 +1717,9 @@ def _uninstall_scope(target: Path, tweek_dir: Path, confirm: bool, scope_label:
1695
1717
  for item in data_removed:
1696
1718
  console.print(f" [green]✓[/green] Removed {item}")
1697
1719
  if not data_removed:
1698
- console.print(f" [dim]-[/dim] No data to remove")
1720
+ console.print(f" [white]-[/white] No data to remove")
1699
1721
  elif tweek_dir.exists():
1700
- console.print("[dim]Tweek data directory (~/.tweek/) was preserved.[/dim]")
1722
+ console.print("[white]Tweek data directory (~/.tweek/) was preserved.[/white]")
1701
1723
 
1702
1724
 
1703
1725
  def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir: Path, confirm: bool):
@@ -1705,28 +1727,28 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1705
1727
  import json
1706
1728
 
1707
1729
  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/)")
1730
+ console.print(" [white]•[/white] Hooks from current project (.claude/settings.json)")
1731
+ console.print(" [white]•[/white] Hooks from global installation (~/.claude/settings.json)")
1732
+ console.print(" [white]•[/white] Tweek skill directories (project + global)")
1733
+ console.print(" [white]•[/white] All backup files")
1734
+ console.print(" [white]•[/white] Tweek data directory (~/.tweek/)")
1713
1735
 
1714
1736
  # Show what exists in ~/.tweek/
1715
1737
  if tweek_dir.exists():
1716
1738
  for item in sorted(tweek_dir.iterdir()):
1717
1739
  if item.is_dir():
1718
- console.print(f" [dim]├── {item.name}/ [/dim]")
1740
+ console.print(f" [white]├── {item.name}/ [/white]")
1719
1741
  else:
1720
- console.print(f" [dim]├── {item.name}[/dim]")
1742
+ console.print(f" [white]├── {item.name}[/white]")
1721
1743
 
1722
- console.print(" [dim]•[/dim] MCP integrations (Claude Desktop, ChatGPT)")
1744
+ console.print(" [white]•[/white] MCP integrations (Claude Desktop, ChatGPT)")
1723
1745
  console.print()
1724
1746
 
1725
1747
  if not confirm:
1726
1748
  console.print("[bold red]Type 'yes' to confirm full removal[/bold red]: ", end="")
1727
1749
  response = input()
1728
1750
  if response.strip().lower() != "yes":
1729
- console.print("[dim]Cancelled[/dim]")
1751
+ console.print("[white]Cancelled[/white]")
1730
1752
  return
1731
1753
 
1732
1754
  console.print()
@@ -1737,17 +1759,17 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1737
1759
  for hook_type in removed_hooks:
1738
1760
  console.print(f" [green]✓[/green] Removed {hook_type} hook from project settings.json")
1739
1761
  if not removed_hooks:
1740
- console.print(f" [dim]-[/dim] Skipped: no project hooks found")
1762
+ console.print(f" [white]-[/white] Skipped: no project hooks found")
1741
1763
 
1742
1764
  if _remove_skill_directory(project_target):
1743
1765
  console.print(f" [green]✓[/green] Removed Tweek skill from project")
1744
1766
  else:
1745
- console.print(f" [dim]-[/dim] Skipped: no project skill directory")
1767
+ console.print(f" [white]-[/white] Skipped: no project skill directory")
1746
1768
 
1747
1769
  if _remove_backup_file(project_target):
1748
1770
  console.print(f" [green]✓[/green] Removed project backup file")
1749
1771
  else:
1750
- console.print(f" [dim]-[/dim] Skipped: no project backup file")
1772
+ console.print(f" [white]-[/white] Skipped: no project backup file")
1751
1773
 
1752
1774
  console.print()
1753
1775
 
@@ -1757,17 +1779,17 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1757
1779
  for hook_type in removed_hooks:
1758
1780
  console.print(f" [green]✓[/green] Removed {hook_type} hook from global settings.json")
1759
1781
  if not removed_hooks:
1760
- console.print(f" [dim]-[/dim] Skipped: no global hooks found")
1782
+ console.print(f" [white]-[/white] Skipped: no global hooks found")
1761
1783
 
1762
1784
  if _remove_skill_directory(global_target):
1763
1785
  console.print(f" [green]✓[/green] Removed Tweek skill from global installation")
1764
1786
  else:
1765
- console.print(f" [dim]-[/dim] Skipped: no global skill directory")
1787
+ console.print(f" [white]-[/white] Skipped: no global skill directory")
1766
1788
 
1767
1789
  if _remove_backup_file(global_target):
1768
1790
  console.print(f" [green]✓[/green] Removed global backup file")
1769
1791
  else:
1770
- console.print(f" [dim]-[/dim] Skipped: no global backup file")
1792
+ console.print(f" [white]-[/white] Skipped: no global backup file")
1771
1793
 
1772
1794
  console.print()
1773
1795
 
@@ -1777,7 +1799,7 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1777
1799
  for item in data_removed:
1778
1800
  console.print(f" [green]✓[/green] Removed {item}")
1779
1801
  if not data_removed:
1780
- console.print(f" [dim]-[/dim] Skipped: no data directory found")
1802
+ console.print(f" [white]-[/white] Skipped: no data directory found")
1781
1803
 
1782
1804
  console.print()
1783
1805
 
@@ -1787,7 +1809,7 @@ def _uninstall_everything(global_target: Path, project_target: Path, tweek_dir:
1787
1809
  for client in mcp_removed:
1788
1810
  console.print(f" [green]✓[/green] Removed {client} MCP integration")
1789
1811
  if not mcp_removed:
1790
- console.print(f" [dim]-[/dim] Skipped: no MCP integrations found")
1812
+ console.print(f" [white]-[/white] Skipped: no MCP integrations found")
1791
1813
 
1792
1814
  console.print()
1793
1815
  console.print("[green]All Tweek data has been removed.[/green]")
@@ -1866,8 +1888,8 @@ def trust(path: str, reason: str, list_trusted: bool):
1866
1888
  ]
1867
1889
 
1868
1890
  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]")
1891
+ console.print("[white]No trusted paths configured.[/white]")
1892
+ console.print("[white]Use 'tweek trust' to trust the current project.[/white]")
1871
1893
  return
1872
1894
 
1873
1895
  if trusted_entries:
@@ -1876,16 +1898,16 @@ def trust(path: str, reason: str, list_trusted: bool):
1876
1898
  entry_reason = entry.get("reason", "")
1877
1899
  console.print(f" [green]✓[/green] {entry['path']}")
1878
1900
  if entry_reason:
1879
- console.print(f" [dim]{entry_reason}[/dim]")
1901
+ console.print(f" [white]{entry_reason}[/white]")
1880
1902
 
1881
1903
  if tool_scoped:
1882
1904
  console.print("\n[bold]Tool-scoped whitelist entries:[/bold]\n")
1883
1905
  for entry in tool_scoped:
1884
1906
  tools = ", ".join(entry.get("tools", []))
1885
1907
  entry_reason = entry.get("reason", "")
1886
- console.print(f" [cyan]○[/cyan] {entry['path']} [dim]({tools})[/dim]")
1908
+ console.print(f" [cyan]○[/cyan] {entry['path']} [white]({tools})[/white]")
1887
1909
  if entry_reason:
1888
- console.print(f" [dim]{entry_reason}[/dim]")
1910
+ console.print(f" [white]{entry_reason}[/white]")
1889
1911
 
1890
1912
  if other_entries:
1891
1913
  console.print("\n[bold]Other whitelist entries:[/bold]\n")
@@ -1896,9 +1918,9 @@ def trust(path: str, reason: str, list_trusted: bool):
1896
1918
  console.print(f" [cyan]○[/cyan] Command: {entry['command_prefix']}")
1897
1919
  entry_reason = entry.get("reason", "")
1898
1920
  if entry_reason:
1899
- console.print(f" [dim]{entry_reason}[/dim]")
1921
+ console.print(f" [white]{entry_reason}[/white]")
1900
1922
 
1901
- console.print(f"\n[dim]Config: {overrides_path}[/dim]")
1923
+ console.print(f"\n[white]Config: {overrides_path}[/white]")
1902
1924
  return
1903
1925
 
1904
1926
  # Resolve path to absolute
@@ -1915,7 +1937,7 @@ def trust(path: str, reason: str, list_trusted: bool):
1915
1937
 
1916
1938
  if already_trusted:
1917
1939
  console.print(f"[green]✓[/green] Already trusted: {resolved}")
1918
- console.print("[dim]Use 'tweek untrust' to remove.[/dim]")
1940
+ console.print("[white]Use 'tweek untrust' to remove.[/white]")
1919
1941
  return
1920
1942
 
1921
1943
  # Add whitelist entry (no tools restriction = all tools exempt)
@@ -1933,8 +1955,8 @@ def trust(path: str, reason: str, list_trusted: bool):
1933
1955
  return
1934
1956
 
1935
1957
  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]")
1958
+ console.print(f" [white]All screening is now skipped for files in this directory.[/white]")
1959
+ console.print(f" [white]To resume screening: tweek untrust {path}[/white]")
1938
1960
 
1939
1961
 
1940
1962
  @main.command(
@@ -1982,7 +2004,7 @@ def untrust(path: str):
1982
2004
 
1983
2005
  if len(whitelist) == original_len:
1984
2006
  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]")
2007
+ console.print("[white]Use 'tweek trust --list' to see all trusted paths.[/white]")
1986
2008
  return
1987
2009
 
1988
2010
  overrides["whitelist"] = whitelist
@@ -1998,7 +2020,7 @@ def untrust(path: str):
1998
2020
  return
1999
2021
 
2000
2022
  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]")
2023
+ console.print(f" [white]Tweek will now screen tool calls for files in this directory.[/white]")
2002
2024
 
2003
2025
 
2004
2026
  @main.command(
@@ -2015,7 +2037,7 @@ def update(check: bool):
2015
2037
  Patterns are stored in ~/.tweek/patterns/ and can be updated
2016
2038
  independently of the Tweek application.
2017
2039
 
2018
- All 215 patterns are included free. PRO tier adds LLM review,
2040
+ All 259 patterns are included free. PRO tier adds LLM review,
2019
2041
  session analysis, and rate limiting.
2020
2042
  """
2021
2043
  import subprocess
@@ -2029,7 +2051,7 @@ def update(check: bool):
2029
2051
  # First time: clone the repo
2030
2052
  if check:
2031
2053
  console.print("[yellow]Patterns not installed.[/yellow]")
2032
- console.print(f"[dim]Run 'tweek update' to install from {patterns_repo}[/dim]")
2054
+ console.print(f"[white]Run 'tweek update' to install from {patterns_repo}[/white]")
2033
2055
  return
2034
2056
 
2035
2057
  console.print(f"[cyan]Installing patterns from {patterns_repo}...[/cyan]")
@@ -2051,15 +2073,15 @@ def update(check: bool):
2051
2073
  data = yaml.safe_load(f)
2052
2074
  count = data.get("pattern_count", len(data.get("patterns", [])))
2053
2075
  free_max = data.get("free_tier_max", 23)
2054
- console.print(f"[dim]Installed {count} patterns ({free_max} free, {count - free_max} pro)[/dim]")
2076
+ console.print(f"[white]Installed {count} patterns ({free_max} free, {count - free_max} pro)[/white]")
2055
2077
 
2056
2078
  except subprocess.CalledProcessError as e:
2057
2079
  console.print(f"[red]✗[/red] Failed to clone patterns: {e.stderr}")
2058
2080
  return
2059
2081
  except FileNotFoundError:
2060
2082
  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]")
2083
+ console.print(" [white]Hint: Install git from https://git-scm.com/downloads[/white]")
2084
+ console.print(" [white]On macOS: xcode-select --install[/white]")
2063
2085
  return
2064
2086
 
2065
2087
  else:
@@ -2080,7 +2102,7 @@ def update(check: bool):
2080
2102
  )
2081
2103
  if "behind" in result2.stdout:
2082
2104
  console.print("[yellow]Updates available.[/yellow]")
2083
- console.print("[dim]Run 'tweek update' to install[/dim]")
2105
+ console.print("[white]Run 'tweek update' to install[/white]")
2084
2106
  else:
2085
2107
  console.print("[green]✓[/green] Patterns are up to date")
2086
2108
  except Exception as e:
@@ -2104,11 +2126,11 @@ def update(check: bool):
2104
2126
 
2105
2127
  # Show what changed
2106
2128
  if result.stdout.strip():
2107
- console.print(f"[dim]{result.stdout.strip()}[/dim]")
2129
+ console.print(f"[white]{result.stdout.strip()}[/white]")
2108
2130
 
2109
2131
  except subprocess.CalledProcessError as e:
2110
2132
  console.print(f"[red]✗[/red] Failed to update patterns: {e.stderr}")
2111
- console.print("[dim]Try: rm -rf ~/.tweek/patterns && tweek update[/dim]")
2133
+ console.print("[white]Try: rm -rf ~/.tweek/patterns && tweek update[/white]")
2112
2134
  return
2113
2135
 
2114
2136
  # Show current version info
@@ -2126,7 +2148,7 @@ def update(check: bool):
2126
2148
  console.print(f"[cyan]Total patterns:[/cyan] {count} (all included free)")
2127
2149
 
2128
2150
  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]")
2151
+ console.print(f"[white]Pro (teams) and Enterprise (compliance) coming soon: gettweek.com[/white]")
2130
2152
 
2131
2153
  except Exception:
2132
2154
  pass
@@ -2159,22 +2181,6 @@ def doctor(verbose: bool, json_out: bool):
2159
2181
  print_doctor_results(checks)
2160
2182
 
2161
2183
 
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
2184
 
2179
2185
  @main.command("upgrade")
2180
2186
  def upgrade():
@@ -2307,7 +2313,7 @@ def audit(path, translate, llm_review, json_out):
2307
2313
  credential theft, data exfiltration, and other attack patterns.
2308
2314
 
2309
2315
  Non-English content is detected and translated to English before
2310
- running all 215 regex patterns. LLM semantic review provides
2316
+ running all 259 regex patterns. LLM semantic review provides
2311
2317
  additional analysis for obfuscated attacks.
2312
2318
 
2313
2319
  \b
@@ -2340,8 +2346,8 @@ def audit(path, translate, llm_review, json_out):
2340
2346
  skills = scan_installed_skills()
2341
2347
 
2342
2348
  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]")
2349
+ console.print("[white]No installed skills found.[/white]")
2350
+ console.print("[white]Specify a file path to audit: tweek audit <path>[/white]")
2345
2351
  return
2346
2352
 
2347
2353
  console.print(f"Found {len(skills)} skill(s)")
@@ -2390,7 +2396,7 @@ def _print_audit_result(result):
2390
2396
  risk_icons = {"safe": "[green]SAFE[/green]", "suspicious": "[yellow]SUSPICIOUS[/yellow]", "dangerous": "[red]DANGEROUS[/red]"}
2391
2397
 
2392
2398
  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]")
2399
+ console.print(f" [white]{result.skill_path}[/white]")
2394
2400
 
2395
2401
  if result.error:
2396
2402
  console.print(f" [red]Error: {result.error}[/red]")
@@ -2405,12 +2411,12 @@ def _print_audit_result(result):
2405
2411
 
2406
2412
  if result.findings:
2407
2413
  table = Table(show_header=True, header_style="bold", box=None, padding=(0, 2))
2408
- table.add_column("Severity", style="dim")
2414
+ table.add_column("Severity", style="white")
2409
2415
  table.add_column("Pattern")
2410
2416
  table.add_column("Description")
2411
- table.add_column("Match", style="dim")
2417
+ table.add_column("Match", style="white")
2412
2418
 
2413
- severity_styles = {"critical": "red bold", "high": "red", "medium": "yellow", "low": "dim"}
2419
+ severity_styles = {"critical": "red bold", "high": "red", "medium": "yellow", "low": "white"}
2414
2420
 
2415
2421
  for finding in result.findings:
2416
2422
  table.add_row(
@@ -2509,7 +2515,7 @@ def quickstart():
2509
2515
  # Step 2: Security preset
2510
2516
  console.print("[bold cyan]Step 2/4: Security Preset[/bold cyan]")
2511
2517
  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]")
2518
+ console.print(" [cyan]2.[/cyan] cautious \u2014 Block dangerous, prompt on risky [white](recommended)[/white]")
2513
2519
  console.print(" [cyan]3.[/cyan] trusted \u2014 Allow most operations, block only dangerous")
2514
2520
  console.print()
2515
2521
 
@@ -2548,17 +2554,16 @@ def quickstart():
2548
2554
  if setup_mcp:
2549
2555
  try:
2550
2556
  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]")
2557
+ console.print("[white]MCP package available. Configure upstream servers in ~/.tweek/config.yaml[/white]")
2558
+ console.print("[white]Then run: tweek mcp proxy[/white]")
2553
2559
  except ImportError:
2554
2560
  print_warning("MCP package not installed. Install with: pip install tweek[mcp]")
2555
2561
  else:
2556
- console.print("[dim]Skipped.[/dim]")
2562
+ console.print("[white]Skipped.[/white]")
2557
2563
 
2558
2564
  console.print()
2559
2565
  console.print("[bold green]Setup complete![/bold green]")
2560
2566
  console.print(" Run [cyan]tweek doctor[/cyan] to verify your installation")
2561
- console.print(" Run [cyan]tweek status[/cyan] to see protection status")
2562
2567
 
2563
2568
 
2564
2569
  def _quickstart_install_hooks(scope: str) -> None:
@@ -2626,21 +2631,31 @@ def _quickstart_install_hooks(scope: str) -> None:
2626
2631
  # =============================================================================
2627
2632
 
2628
2633
  @main.group(
2634
+ invoke_without_command=True,
2629
2635
  epilog="""\b
2630
2636
  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)
2637
+ tweek protect Interactive wizard detect & protect all tools
2638
+ tweek protect --status Show protection status for all tools
2639
+ tweek protect claude-code Install Claude Code hooks
2640
+ tweek protect openclaw One-command OpenClaw protection
2641
+ tweek protect claude-desktop Configure Claude Desktop integration
2642
+ tweek protect chatgpt Set up ChatGPT Desktop integration
2643
+ tweek protect gemini Configure Gemini CLI integration
2635
2644
  """
2636
2645
  )
2637
- def protect():
2638
- """Set up Tweek protection for a specific AI agent.
2646
+ @click.option("--status", is_flag=True, help="Show protection status for all tools")
2647
+ @click.pass_context
2648
+ def protect(ctx, status):
2649
+ """Set up Tweek protection for AI tools.
2639
2650
 
2640
- One-command setup that auto-detects, configures, and starts
2641
- screening all tool calls for your AI assistant.
2651
+ When run without a subcommand, launches an interactive wizard
2652
+ that auto-detects installed AI tools and offers to protect them.
2642
2653
  """
2643
- pass
2654
+ if status:
2655
+ _show_protection_status()
2656
+ return
2657
+ if ctx.invoked_subcommand is None:
2658
+ _run_protect_wizard()
2644
2659
 
2645
2660
 
2646
2661
  @protect.command(
@@ -2688,11 +2703,11 @@ def protect_openclaw(port, paranoid, preset):
2688
2703
  console.print()
2689
2704
  console.print("[red]OpenClaw not detected on this system.[/red]")
2690
2705
  console.print()
2691
- console.print("[dim]Install OpenClaw first:[/dim]")
2706
+ console.print("[white]Install OpenClaw first:[/white]")
2692
2707
  console.print(" npm install -g openclaw")
2693
2708
  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]")
2709
+ console.print("[white]Or if OpenClaw is installed in a non-standard location,[/white]")
2710
+ console.print("[white]specify the gateway port manually:[/white]")
2696
2711
  console.print(" tweek protect openclaw --port 18789")
2697
2712
  return
2698
2713
 
@@ -2709,7 +2724,7 @@ def protect_openclaw(port, paranoid, preset):
2709
2724
  elif openclaw["process_running"]:
2710
2725
  console.print(" [yellow](process running, gateway inactive)[/yellow]")
2711
2726
  else:
2712
- console.print(" [dim](not running)[/dim]")
2727
+ console.print(" [white](not running)[/white]")
2713
2728
 
2714
2729
  if openclaw["config_path"]:
2715
2730
  console.print(f" Config: {openclaw['config_path']}")
@@ -2726,14 +2741,14 @@ def protect_openclaw(port, paranoid, preset):
2726
2741
 
2727
2742
  # Show configuration
2728
2743
  console.print(f" Scanner: port {result.scanner_port} -> wrapping OpenClaw gateway")
2729
- console.print(f" Preset: {result.preset} (215 patterns + rate limiting)")
2744
+ console.print(f" Preset: {result.preset} (259 patterns + rate limiting)")
2730
2745
 
2731
2746
  # Check for API key
2732
2747
  anthropic_key = os.environ.get("ANTHROPIC_API_KEY")
2733
2748
  if anthropic_key:
2734
2749
  console.print(" LLM Review: [green]active[/green] (ANTHROPIC_API_KEY found)")
2735
2750
  else:
2736
- console.print(" LLM Review: [dim]available (set ANTHROPIC_API_KEY for semantic analysis)[/dim]")
2751
+ console.print(" LLM Review: [white]available (set ANTHROPIC_API_KEY for semantic analysis)[/white]")
2737
2752
 
2738
2753
  # Show warnings
2739
2754
  for warning in result.warnings:
@@ -2743,53 +2758,366 @@ def protect_openclaw(port, paranoid, preset):
2743
2758
 
2744
2759
  if not openclaw["gateway_active"]:
2745
2760
  console.print("[yellow]Note: OpenClaw gateway is not currently running.[/yellow]")
2746
- console.print("[dim]Protection will activate when OpenClaw starts.[/dim]")
2761
+ console.print("[white]Protection will activate when OpenClaw starts.[/white]")
2747
2762
  console.print()
2748
2763
 
2749
2764
  console.print("[green]Protection configured.[/green] Screening all OpenClaw tool calls.")
2750
2765
  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]")
2766
+ console.print("[white]Verify: tweek doctor[/white]")
2767
+ console.print("[white]Logs: tweek logs show[/white]")
2768
+ console.print("[white]Stop: tweek proxy stop[/white]")
2754
2769
 
2755
2770
 
2756
2771
  @protect.command(
2757
- "claude",
2772
+ "claude-code",
2758
2773
  epilog="""\b
2759
2774
  Examples:
2760
- tweek protect claude Install Claude Code hooks (current project)
2761
- tweek protect claude --global Install globally (all projects)
2775
+ tweek protect claude-code Install for current project
2776
+ tweek protect claude-code --global Install globally (all projects)
2777
+ tweek protect claude-code --quick Zero-prompt install with defaults
2778
+ tweek protect claude-code --preset paranoid Apply paranoid security preset
2762
2779
  """
2763
2780
  )
2764
2781
  @click.option("--global", "install_global", is_flag=True, default=False,
2765
2782
  help="Install globally to ~/.claude/ (protects all projects)")
2783
+ @click.option("--dev-test", is_flag=True, hidden=True,
2784
+ help="Install to test environment (for Tweek development only)")
2785
+ @click.option("--backup/--no-backup", default=True,
2786
+ help="Backup existing hooks before installation")
2787
+ @click.option("--skip-env-scan", is_flag=True,
2788
+ help="Skip scanning for .env files to migrate")
2789
+ @click.option("--interactive", "-i", is_flag=True,
2790
+ help="Interactively configure security settings")
2766
2791
  @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):
2792
+ help="Apply a security preset (skip interactive)")
2793
+ @click.option("--ai-defaults", is_flag=True,
2794
+ help="Let AI suggest default settings based on detected skills")
2795
+ @click.option("--with-sandbox", is_flag=True,
2796
+ help="Prompt to install sandbox tool if not available (Linux only)")
2797
+ @click.option("--force-proxy", is_flag=True,
2798
+ help="Force Tweek proxy to override existing proxy configurations (e.g., openclaw)")
2799
+ @click.option("--skip-proxy-check", is_flag=True,
2800
+ help="Skip checking for existing proxy configurations")
2801
+ @click.option("--quick", is_flag=True,
2802
+ help="Zero-prompt install with cautious defaults (skips env scan and proxy check)")
2803
+ 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
2804
  """Install Tweek hooks for Claude Code.
2771
2805
 
2772
- This is equivalent to 'tweek install' -- installs PreToolUse
2773
- and PostToolUse hooks to screen all Claude Code tool calls.
2806
+ Installs PreToolUse and PostToolUse hooks to screen all
2807
+ Claude Code tool calls through Tweek's security pipeline.
2774
2808
  """
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,
2809
+ _install_claude_code_hooks(
2780
2810
  install_global=install_global,
2781
- dev_test=False,
2782
- backup=True,
2783
- skip_env_scan=False,
2784
- interactive=False,
2811
+ dev_test=dev_test,
2812
+ backup=backup,
2813
+ skip_env_scan=skip_env_scan,
2814
+ interactive=interactive,
2785
2815
  preset=preset,
2786
- ai_defaults=False,
2787
- with_sandbox=False,
2788
- force_proxy=False,
2789
- skip_proxy_check=False,
2816
+ ai_defaults=ai_defaults,
2817
+ with_sandbox=with_sandbox,
2818
+ force_proxy=force_proxy,
2819
+ skip_proxy_check=skip_proxy_check,
2820
+ quick=quick,
2790
2821
  )
2791
2822
 
2792
2823
 
2824
+ @protect.command("claude-desktop")
2825
+ def protect_claude_desktop():
2826
+ """Configure Tweek as MCP server for Claude Desktop."""
2827
+ _protect_mcp_client("claude-desktop")
2828
+
2829
+
2830
+ @protect.command("chatgpt")
2831
+ def protect_chatgpt():
2832
+ """Configure Tweek as MCP server for ChatGPT Desktop."""
2833
+ _protect_mcp_client("chatgpt")
2834
+
2835
+
2836
+ @protect.command("gemini")
2837
+ def protect_gemini():
2838
+ """Configure Tweek as MCP server for Gemini CLI."""
2839
+ _protect_mcp_client("gemini")
2840
+
2841
+
2842
+ def _protect_mcp_client(client_name: str):
2843
+ """Shared logic for MCP client protection commands."""
2844
+ try:
2845
+ from tweek.mcp.clients import get_client
2846
+
2847
+ handler = get_client(client_name)
2848
+ result = handler.install()
2849
+
2850
+ if result.get("success"):
2851
+ console.print(f"[green]{result.get('message', 'Installed successfully')}[/green]")
2852
+ if result.get("config_path"):
2853
+ console.print(f" Config: {result['config_path']}")
2854
+ if result.get("backup"):
2855
+ console.print(f" Backup: {result['backup']}")
2856
+ if result.get("instructions"):
2857
+ console.print()
2858
+ for line in result["instructions"]:
2859
+ console.print(f" {line}")
2860
+ else:
2861
+ console.print(f"[red]{result.get('error', 'Installation failed')}[/red]")
2862
+ except Exception as e:
2863
+ console.print(f"[red]Error: {e}[/red]")
2864
+
2865
+
2866
+ # =============================================================================
2867
+ # PROTECT WIZARD & STATUS HELPERS
2868
+ # =============================================================================
2869
+
2870
+
2871
+ def _detect_all_tools():
2872
+ """Detect all supported AI tools and their protection status.
2873
+
2874
+ Returns list of (tool_id, label, installed, protected, detail) tuples.
2875
+ """
2876
+ import shutil
2877
+ import json
2878
+
2879
+ tools = []
2880
+
2881
+ # Claude Code
2882
+ claude_installed = shutil.which("claude") is not None
2883
+ claude_protected = _has_tweek_at(Path("~/.claude").expanduser()) if claude_installed else False
2884
+ tools.append((
2885
+ "claude-code", "Claude Code", claude_installed, claude_protected,
2886
+ "Hooks in ~/.claude/settings.json" if claude_protected else "",
2887
+ ))
2888
+
2889
+ # OpenClaw
2890
+ oc_installed = False
2891
+ oc_protected = False
2892
+ oc_detail = ""
2893
+ try:
2894
+ from tweek.integrations.openclaw import detect_openclaw_installation
2895
+ openclaw = detect_openclaw_installation()
2896
+ oc_installed = openclaw.get("installed", False)
2897
+ if oc_installed:
2898
+ oc_protected = openclaw.get("tweek_configured", False)
2899
+ oc_detail = f"Gateway port {openclaw.get('gateway_port', '?')}"
2900
+ except Exception:
2901
+ pass
2902
+ tools.append(("openclaw", "OpenClaw", oc_installed, oc_protected, oc_detail))
2903
+
2904
+ # MCP clients
2905
+ mcp_configs = [
2906
+ ("claude-desktop", "Claude Desktop",
2907
+ Path("~/Library/Application Support/Claude/claude_desktop_config.json").expanduser()),
2908
+ ("chatgpt", "ChatGPT Desktop",
2909
+ Path("~/Library/Application Support/com.openai.chat/developer_settings.json").expanduser()),
2910
+ ("gemini", "Gemini CLI",
2911
+ Path("~/.gemini/settings.json").expanduser()),
2912
+ ]
2913
+ for tool_id, label, config_path in mcp_configs:
2914
+ installed = config_path.exists()
2915
+ protected = False
2916
+ if installed:
2917
+ try:
2918
+ with open(config_path) as f:
2919
+ data = json.load(f)
2920
+ mcp_servers = data.get("mcpServers", {})
2921
+ protected = "tweek-security" in mcp_servers or "tweek" in mcp_servers
2922
+ except Exception:
2923
+ pass
2924
+ detail = str(config_path) if protected else ""
2925
+ tools.append((tool_id, label, installed, protected, detail))
2926
+
2927
+ return tools
2928
+
2929
+
2930
+ def _run_protect_wizard():
2931
+ """Interactive wizard: detect tools and ask Y/n for each one."""
2932
+ console.print(TWEEK_BANNER, style="cyan")
2933
+ console.print("[bold]Tweek Protection Wizard[/bold]\n")
2934
+ console.print("Scanning for AI tools...\n")
2935
+
2936
+ tools = _detect_all_tools()
2937
+
2938
+ # Show detection summary
2939
+ detected = [(tid, label, prot) for tid, label, inst, prot, _ in tools if inst]
2940
+ not_detected = [label for _, label, inst, _, _ in tools if not inst]
2941
+
2942
+ if not_detected:
2943
+ for label in not_detected:
2944
+ console.print(f" [white]{label:<20}[/white] [white]not found[/white]")
2945
+
2946
+ if not detected:
2947
+ console.print("\n[yellow]No AI tools detected on this system.[/yellow]")
2948
+ return
2949
+
2950
+ # Show already-protected tools
2951
+ already_protected = [(tid, label) for tid, label, prot in detected if prot]
2952
+ unprotected = [(tid, label) for tid, label, prot in detected if not prot]
2953
+
2954
+ for _, label in already_protected:
2955
+ console.print(f" [green]{label:<20} protected[/green]")
2956
+
2957
+ if not unprotected:
2958
+ console.print(f"\n[green]All {len(already_protected)} detected tool(s) already protected.[/green]")
2959
+ console.print("Run 'tweek status' to see details.")
2960
+ return
2961
+
2962
+ for _, label in unprotected:
2963
+ console.print(f" [yellow]{label:<20} not protected[/yellow]")
2964
+
2965
+ # Ask for preset first (applies to all)
2966
+ console.print()
2967
+ console.print("[bold]Security preset:[/bold]")
2968
+ console.print(" [bold]1.[/bold] cautious [white](recommended)[/white] — screen risky & dangerous tools")
2969
+ console.print(" [bold]2.[/bold] paranoid — screen everything except safe tools")
2970
+ console.print(" [bold]3.[/bold] trusted — only screen dangerous tools")
2971
+ console.print()
2972
+ preset_choice = click.prompt("Select preset", type=click.IntRange(1, 3), default=1)
2973
+ preset = ["cautious", "paranoid", "trusted"][preset_choice - 1]
2974
+
2975
+ # Walk through each unprotected tool
2976
+ console.print()
2977
+ protected_count = 0
2978
+ skipped_count = 0
2979
+
2980
+ for tool_id, label in unprotected:
2981
+ protect_it = click.confirm(f" Protect {label}?", default=True)
2982
+
2983
+ if not protect_it:
2984
+ console.print(f" [white]skipped[/white]")
2985
+ skipped_count += 1
2986
+ continue
2987
+
2988
+ try:
2989
+ if tool_id == "claude-code":
2990
+ _install_claude_code_hooks(
2991
+ install_global=True, dev_test=False, backup=True,
2992
+ skip_env_scan=True, interactive=False, preset=preset,
2993
+ ai_defaults=False, with_sandbox=False, force_proxy=False,
2994
+ skip_proxy_check=True, quick=True,
2995
+ )
2996
+ elif tool_id == "openclaw":
2997
+ from tweek.integrations.openclaw import setup_openclaw_protection
2998
+ result = setup_openclaw_protection(preset=preset)
2999
+ if result.success:
3000
+ console.print(f" [green]done[/green]")
3001
+ else:
3002
+ console.print(f" [red]failed: {result.error}[/red]")
3003
+ continue
3004
+ elif tool_id in ("claude-desktop", "chatgpt", "gemini"):
3005
+ _protect_mcp_client(tool_id)
3006
+ protected_count += 1
3007
+ except Exception as e:
3008
+ console.print(f" [red]error: {e}[/red]")
3009
+
3010
+ console.print()
3011
+ if protected_count:
3012
+ console.print(f"[green]Protected {protected_count} tool(s).[/green]", end="")
3013
+ if skipped_count:
3014
+ console.print(f" [white]Skipped {skipped_count}.[/white]", end="")
3015
+ console.print()
3016
+ console.print("Run 'tweek status' to see the full dashboard.")
3017
+
3018
+
3019
+ def _run_unprotect_wizard():
3020
+ """Interactive wizard: detect protected tools and ask Y/n to unprotect each."""
3021
+ console.print(TWEEK_BANNER, style="cyan")
3022
+ console.print("[bold]Tweek Unprotect Wizard[/bold]\n")
3023
+ console.print("Scanning for protected AI tools...\n")
3024
+
3025
+ tools = _detect_all_tools()
3026
+ tweek_dir = Path("~/.tweek").expanduser()
3027
+ global_target = Path("~/.claude").expanduser()
3028
+ project_target = Path.cwd() / ".claude"
3029
+
3030
+ protected = [(tid, label) for tid, label, inst, prot, _ in tools if inst and prot]
3031
+
3032
+ if not protected:
3033
+ console.print("[yellow]No protected tools found.[/yellow]")
3034
+ return
3035
+
3036
+ for _, label in protected:
3037
+ console.print(f" [green]{label:<20} protected[/green]")
3038
+
3039
+ console.print()
3040
+ removed_count = 0
3041
+ skipped_count = 0
3042
+
3043
+ for tool_id, label in protected:
3044
+ remove_it = click.confirm(f" Remove protection from {label}?", default=False)
3045
+
3046
+ if not remove_it:
3047
+ console.print(f" [white]kept[/white]")
3048
+ skipped_count += 1
3049
+ continue
3050
+
3051
+ try:
3052
+ if tool_id == "claude-code":
3053
+ _uninstall_scope(global_target, tweek_dir, confirm=True, scope_label="global")
3054
+ elif tool_id in ("claude-desktop", "chatgpt", "gemini"):
3055
+ from tweek.mcp.clients import get_client
3056
+ handler = get_client(tool_id)
3057
+ result = handler.uninstall()
3058
+ if result.get("success"):
3059
+ console.print(f" [green]{result.get('message', 'removed')}[/green]")
3060
+ else:
3061
+ console.print(f" [red]{result.get('error', 'failed')}[/red]")
3062
+ continue
3063
+ elif tool_id == "openclaw":
3064
+ console.print(" [white]Manual step: remove tweek plugin from openclaw.json[/white]")
3065
+ removed_count += 1
3066
+ except Exception as e:
3067
+ console.print(f" [red]error: {e}[/red]")
3068
+
3069
+ console.print()
3070
+ if removed_count:
3071
+ console.print(f"[green]Removed protection from {removed_count} tool(s).[/green]", end="")
3072
+ if skipped_count:
3073
+ console.print(f" [white]Kept {skipped_count}.[/white]", end="")
3074
+ console.print()
3075
+
3076
+
3077
+ def _show_protection_status():
3078
+ """Show protection status dashboard for all AI tools."""
3079
+ console.print(TWEEK_BANNER, style="cyan")
3080
+
3081
+ tools = _detect_all_tools()
3082
+
3083
+ # Build status table
3084
+ table = Table(title="Protection Status", show_lines=False)
3085
+ table.add_column("Tool", style="cyan", min_width=18)
3086
+ table.add_column("Installed", justify="center", min_width=10)
3087
+ table.add_column("Protected", justify="center", min_width=10)
3088
+ table.add_column("Details")
3089
+
3090
+ detected_count = 0
3091
+ protected_count = 0
3092
+
3093
+ for tool_id, label, installed, protected, detail in tools:
3094
+ if installed:
3095
+ detected_count += 1
3096
+ if protected:
3097
+ protected_count += 1
3098
+
3099
+ table.add_row(
3100
+ label,
3101
+ "[green]yes[/green]" if installed else "[white]no[/white]",
3102
+ "[green]yes[/green]" if protected else "[yellow]no[/yellow]" if installed else "[white]—[/white]",
3103
+ detail,
3104
+ )
3105
+
3106
+ console.print(table)
3107
+ console.print()
3108
+
3109
+ # Summary line
3110
+ unprotected_count = detected_count - protected_count
3111
+ if detected_count == 0:
3112
+ console.print("[yellow]No AI tools detected.[/yellow]")
3113
+ elif unprotected_count == 0:
3114
+ console.print(f"[green]{protected_count}/{detected_count} detected tools protected.[/green]")
3115
+ else:
3116
+ console.print(f"[yellow]{protected_count}/{detected_count} detected tools protected. {unprotected_count} unprotected.[/yellow]")
3117
+ console.print("[white]Run 'tweek protect' to set up protection.[/white]")
3118
+ console.print()
3119
+
3120
+
2793
3121
  # =============================================================================
2794
3122
  # CONFIG COMMANDS
2795
3123
  # =============================================================================
@@ -2874,7 +3202,7 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2874
3202
  }
2875
3203
 
2876
3204
  source_styles = {
2877
- "default": "dim",
3205
+ "default": "white",
2878
3206
  "user": "cyan",
2879
3207
  "project": "magenta",
2880
3208
  }
@@ -2883,7 +3211,7 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2883
3211
  table = Table(title="Tool Security Tiers")
2884
3212
  table.add_column("Tool", style="bold")
2885
3213
  table.add_column("Tier")
2886
- table.add_column("Source", style="dim")
3214
+ table.add_column("Source", style="white")
2887
3215
  table.add_column("Description")
2888
3216
 
2889
3217
  for tool in cfg.list_tools():
@@ -2903,7 +3231,7 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2903
3231
  table = Table(title="Skill Security Tiers")
2904
3232
  table.add_column("Skill", style="bold")
2905
3233
  table.add_column("Tier")
2906
- table.add_column("Source", style="dim")
3234
+ table.add_column("Source", style="white")
2907
3235
  table.add_column("Description")
2908
3236
 
2909
3237
  for skill in cfg.list_skills():
@@ -2918,8 +3246,8 @@ def config_list(show_tools: bool, show_skills: bool, summary: bool):
2918
3246
 
2919
3247
  console.print(table)
2920
3248
 
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]")
3249
+ console.print("\n[white]Tiers: safe (no checks) → default (regex) → risky (+LLM) → dangerous (+sandbox)[/white]")
3250
+ console.print("[white]Sources: default (built-in), user (~/.tweek/config.yaml), project (.tweek/config.yaml)[/white]")
2923
3251
 
2924
3252
 
2925
3253
  @config.command("set",
@@ -2982,11 +3310,11 @@ def config_preset(preset_name: str, scope: str):
2982
3310
  console.print(f"[green]✓[/green] Applied [bold]{preset_name}[/bold] preset ({scope} config)")
2983
3311
 
2984
3312
  if preset_name == "paranoid":
2985
- console.print("[dim]All tools require screening, Bash commands always sandboxed[/dim]")
3313
+ console.print("[white]All tools require screening, Bash commands always sandboxed[/white]")
2986
3314
  elif preset_name == "cautious":
2987
- console.print("[dim]Balanced: read-only tools safe, Bash dangerous[/dim]")
3315
+ console.print("[white]Balanced: read-only tools safe, Bash dangerous[/white]")
2988
3316
  elif preset_name == "trusted":
2989
- console.print("[dim]Minimal prompts: only high-risk patterns trigger alerts[/dim]")
3317
+ console.print("[white]Minimal prompts: only high-risk patterns trigger alerts[/white]")
2990
3318
 
2991
3319
 
2992
3320
  @config.command("reset",
@@ -3011,7 +3339,7 @@ def config_reset(skill: str, tool: str, reset_all: bool, scope: str, confirm: bo
3011
3339
 
3012
3340
  if reset_all:
3013
3341
  if not confirm and not click.confirm(f"Reset ALL {scope} configuration?"):
3014
- console.print("[dim]Cancelled[/dim]")
3342
+ console.print("[white]Cancelled[/white]")
3015
3343
  return
3016
3344
  cfg.reset_all(scope=scope)
3017
3345
  console.print(f"[green]✓[/green] Reset all {scope} configuration to defaults")
@@ -3069,7 +3397,7 @@ def config_validate(scope: str, json_out: bool):
3069
3397
  console.print()
3070
3398
  console.print("[bold]Configuration Validation[/bold]")
3071
3399
  console.print("\u2500" * 40)
3072
- console.print(f"[dim]Scope: {scope}[/dim]")
3400
+ console.print(f"[white]Scope: {scope}[/white]")
3073
3401
  console.print()
3074
3402
 
3075
3403
  if not issues:
@@ -3085,11 +3413,11 @@ def config_validate(scope: str, json_out: bool):
3085
3413
  level_styles = {
3086
3414
  "error": "[red]ERROR[/red]",
3087
3415
  "warning": "[yellow]WARN[/yellow] ",
3088
- "info": "[dim]INFO[/dim] ",
3416
+ "info": "[white]INFO[/white] ",
3089
3417
  }
3090
3418
 
3091
3419
  for issue in issues:
3092
- style = level_styles.get(issue.level, "[dim]???[/dim] ")
3420
+ style = level_styles.get(issue.level, "[white]???[/white] ")
3093
3421
  msg = f" {style} {issue.key} \u2192 {issue.message}"
3094
3422
  if issue.suggestion:
3095
3423
  msg += f" {issue.suggestion}"
@@ -3201,7 +3529,7 @@ def config_llm(verbose: bool, validate: bool):
3201
3529
  console.print()
3202
3530
  console.print(" [yellow]Status:[/yellow] Disabled (no provider available)")
3203
3531
  console.print()
3204
- console.print(" [dim]To enable, set one of:[/dim]")
3532
+ console.print(" [white]To enable, set one of:[/white]")
3205
3533
  console.print(" ANTHROPIC_API_KEY, OPENAI_API_KEY, or GOOGLE_API_KEY")
3206
3534
  console.print(" Or install Ollama: [cyan]https://ollama.ai[/cyan]")
3207
3535
  console.print()
@@ -3236,8 +3564,8 @@ def config_llm(verbose: bool, validate: bool):
3236
3564
  for m in server.all_models:
3237
3565
  console.print(f" - {m}")
3238
3566
  else:
3239
- console.print(" [dim]No local LLM server detected[/dim]")
3240
- console.print(" [dim]Checked: Ollama (localhost:11434), LM Studio (localhost:1234)[/dim]")
3567
+ console.print(" [white]No local LLM server detected[/white]")
3568
+ console.print(" [white]Checked: Ollama (localhost:11434), LM Studio (localhost:1234)[/white]")
3241
3569
  except Exception as e:
3242
3570
  console.print(f" [yellow]Detection error: {e}[/yellow]")
3243
3571
 
@@ -3275,8 +3603,8 @@ def config_llm(verbose: bool, validate: bool):
3275
3603
  console.print(f" [green]PASSED[/green] ({score:.0%})")
3276
3604
  else:
3277
3605
  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]")
3606
+ console.print(" [white]This model may not reliably classify security threats.[/white]")
3607
+ console.print(" [white]Try a larger model: ollama pull qwen2.5:7b-instruct[/white]")
3280
3608
  except Exception as e:
3281
3609
  console.print(f" [red]Validation error: {e}[/red]")
3282
3610
 
@@ -3306,8 +3634,8 @@ def vault_store(skill: str, key: str, value: Optional[str]):
3306
3634
 
3307
3635
  if not VAULT_AVAILABLE:
3308
3636
  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]")
3637
+ console.print(" [white]Hint: Install keyring support: pip install keyring[/white]")
3638
+ console.print(" [white]On macOS, keyring uses Keychain. On Linux, install gnome-keyring or kwallet.[/white]")
3311
3639
  return
3312
3640
 
3313
3641
  caps = get_capabilities()
@@ -3323,13 +3651,13 @@ def vault_store(skill: str, key: str, value: Optional[str]):
3323
3651
  vault_instance = get_vault()
3324
3652
  if vault_instance.store(skill, key, value):
3325
3653
  console.print(f"[green]\u2713[/green] Stored {key} for skill '{skill}'")
3326
- console.print(f"[dim]Backend: {caps.vault_backend}[/dim]")
3654
+ console.print(f"[white]Backend: {caps.vault_backend}[/white]")
3327
3655
  else:
3328
3656
  console.print(f"[red]\u2717[/red] Failed to store credential")
3329
- console.print(" [dim]Hint: Check your keyring backend is unlocked and accessible[/dim]")
3657
+ console.print(" [white]Hint: Check your keyring backend is unlocked and accessible[/white]")
3330
3658
  except Exception as e:
3331
3659
  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]")
3660
+ console.print(" [white]Hint: Check your keyring backend is unlocked and accessible[/white]")
3333
3661
 
3334
3662
 
3335
3663
  @vault.command("get",
@@ -3347,7 +3675,7 @@ def vault_get(skill: str, key: str):
3347
3675
 
3348
3676
  if not VAULT_AVAILABLE:
3349
3677
  console.print("[red]\u2717[/red] Vault not available.")
3350
- console.print(" [dim]Hint: Install keyring support: pip install keyring[/dim]")
3678
+ console.print(" [white]Hint: Install keyring support: pip install keyring[/white]")
3351
3679
  return
3352
3680
 
3353
3681
  vault_instance = get_vault()
@@ -3361,7 +3689,7 @@ def vault_get(skill: str, key: str):
3361
3689
  console.print(value)
3362
3690
  else:
3363
3691
  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))
3692
+ console.print(" [white]Hint: Store it with: tweek vault store {skill} {key} <value>[/white]".format(skill=skill, key=key))
3365
3693
 
3366
3694
 
3367
3695
  @vault.command("migrate-env",
@@ -3401,7 +3729,7 @@ def vault_migrate_env(dry_run: bool, env_file: str, skill: str):
3401
3729
  successful = sum(1 for _, s in results if s)
3402
3730
  console.print(f"\n[green]✓[/green] {'Would migrate' if dry_run else 'Migrated'} {successful} credentials to skill '{skill}'")
3403
3731
  else:
3404
- console.print("[dim]No credentials found to migrate[/dim]")
3732
+ console.print("[white]No credentials found to migrate[/white]")
3405
3733
 
3406
3734
  except Exception as e:
3407
3735
  console.print(f"[red]✗[/red] Migration failed: {e}")
@@ -3468,16 +3796,16 @@ def license_status():
3468
3796
  console.print(f"[bold]License Tier:[/bold] [{tier_color}]{lic.tier.value.upper()}[/{tier_color}]")
3469
3797
 
3470
3798
  if info:
3471
- console.print(f"[dim]Licensed to: {info.email}[/dim]")
3799
+ console.print(f"[white]Licensed to: {info.email}[/white]")
3472
3800
  if info.expires_at:
3473
3801
  from datetime import datetime
3474
3802
  exp_date = datetime.fromtimestamp(info.expires_at).strftime("%Y-%m-%d")
3475
3803
  if info.is_expired:
3476
3804
  console.print(f"[red]Expired: {exp_date}[/red]")
3477
3805
  else:
3478
- console.print(f"[dim]Expires: {exp_date}[/dim]")
3806
+ console.print(f"[white]Expires: {exp_date}[/white]")
3479
3807
  else:
3480
- console.print("[dim]Expires: Never[/dim]")
3808
+ console.print("[white]Expires: Never[/white]")
3481
3809
  console.print()
3482
3810
 
3483
3811
  # Features table
@@ -3494,7 +3822,7 @@ def license_status():
3494
3822
 
3495
3823
  for feature, required_tier in feature_tiers.items():
3496
3824
  has_it = lic.has_feature(feature)
3497
- status = "[green]✓[/green]" if has_it else "[dim]○[/dim]"
3825
+ status = "[green]✓[/green]" if has_it else "[white]○[/white]"
3498
3826
  tier_display = required_tier.value.upper()
3499
3827
  if required_tier == Tier.PRO:
3500
3828
  tier_display = f"[cyan]{tier_display}[/cyan]"
@@ -3506,7 +3834,7 @@ def license_status():
3506
3834
  if lic.tier == Tier.FREE:
3507
3835
  console.print()
3508
3836
  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]")
3837
+ console.print("[white]Pro (teams) and Enterprise (compliance) coming soon: gettweek.com[/white]")
3510
3838
 
3511
3839
 
3512
3840
  @license.command("activate",
@@ -3526,7 +3854,7 @@ def license_activate(license_key: str):
3526
3854
  if success:
3527
3855
  console.print(f"[green]✓[/green] {message}")
3528
3856
  console.print()
3529
- console.print("[dim]Run 'tweek license status' to see available features[/dim]")
3857
+ console.print("[white]Run 'tweek license status' to see available features[/white]")
3530
3858
  else:
3531
3859
  console.print(f"[red]✗[/red] {message}")
3532
3860
 
@@ -3546,7 +3874,7 @@ def license_deactivate(confirm: bool):
3546
3874
  if not confirm:
3547
3875
  console.print("[yellow]Deactivate license and revert to FREE tier?[/yellow] ", end="")
3548
3876
  if not click.confirm(""):
3549
- console.print("[dim]Cancelled[/dim]")
3877
+ console.print("[white]Cancelled[/white]")
3550
3878
  return
3551
3879
 
3552
3880
  lic = get_license()
@@ -3624,7 +3952,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3624
3952
  table.add_column("Severity")
3625
3953
  table.add_column("Count", justify="right")
3626
3954
 
3627
- severity_styles = {"critical": "red", "high": "yellow", "medium": "blue", "low": "dim"}
3955
+ severity_styles = {"critical": "red", "high": "yellow", "medium": "blue", "low": "white"}
3628
3956
  for pattern in stat_data['top_patterns']:
3629
3957
  sev = pattern['severity'] or "unknown"
3630
3958
  style = severity_styles.get(sev, "white")
@@ -3661,7 +3989,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3661
3989
  et = EventType(event_type)
3662
3990
  except ValueError:
3663
3991
  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]")
3992
+ console.print(f"[white]Valid types: {', '.join(e.value for e in EventType)}[/white]")
3665
3993
  return
3666
3994
 
3667
3995
  events = logger.get_recent_events(limit=limit, event_type=et, tool_name=tool)
@@ -3672,7 +4000,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3672
4000
  return
3673
4001
 
3674
4002
  table = Table(title=title)
3675
- table.add_column("Time", style="dim")
4003
+ table.add_column("Time", style="white")
3676
4004
  table.add_column("Type", style="cyan")
3677
4005
  table.add_column("Tool", style="green")
3678
4006
  table.add_column("Tier")
@@ -3713,7 +4041,7 @@ def logs_show(limit: int, event_type: str, tool: str, blocked: bool, stats: bool
3713
4041
  )
3714
4042
 
3715
4043
  console.print(table)
3716
- console.print(f"\n[dim]Showing {len(events)} events. Use --limit to see more.[/dim]")
4044
+ console.print(f"\n[white]Showing {len(events)} events. Use --limit to see more.[/white]")
3717
4045
 
3718
4046
 
3719
4047
  @logs.command("export",
@@ -3764,7 +4092,7 @@ def logs_clear(days: int, confirm: bool):
3764
4092
 
3765
4093
  console.print(f"[yellow]{msg}[/yellow] ", end="")
3766
4094
  if not click.confirm(""):
3767
- console.print("[dim]Cancelled[/dim]")
4095
+ console.print("[white]Cancelled[/white]")
3768
4096
  return
3769
4097
 
3770
4098
  logger = get_logger()
@@ -3776,7 +4104,7 @@ def logs_clear(days: int, confirm: bool):
3776
4104
  else:
3777
4105
  console.print(f"[green]Cleared {deleted} event(s)[/green]")
3778
4106
  else:
3779
- console.print("[dim]No events to clear[/dim]")
4107
+ console.print("[white]No events to clear[/white]")
3780
4108
 
3781
4109
 
3782
4110
  @logs.command("bundle",
@@ -3814,11 +4142,11 @@ def logs_bundle(output: str, days: int, no_redact: bool, dry_run: bool):
3814
4142
  size = item.get("size")
3815
4143
  size_str = f" ({size:,} bytes)" if size else ""
3816
4144
  if "not found" in status:
3817
- console.print(f" [dim] SKIP {name} ({status})[/dim]")
4145
+ console.print(f" [white] SKIP {name} ({status})[/white]")
3818
4146
  else:
3819
4147
  console.print(f" [green] ADD {name}{size_str}[/green]")
3820
4148
  console.print()
3821
- console.print("[dim]No files will be collected in dry-run mode.[/dim]")
4149
+ console.print("[white]No files will be collected in dry-run mode.[/white]")
3822
4150
  return
3823
4151
 
3824
4152
  # Determine output path
@@ -3836,9 +4164,9 @@ def logs_bundle(output: str, days: int, no_redact: bool, dry_run: bool):
3836
4164
  result = collector.create_bundle(output_path)
3837
4165
  size = result.stat().st_size
3838
4166
  console.print(f"\n[green]Bundle created: {result}[/green]")
3839
- console.print(f"[dim]Size: {size:,} bytes[/dim]")
4167
+ console.print(f"[white]Size: {size:,} bytes[/white]")
3840
4168
  if not no_redact:
3841
- console.print("[dim]Sensitive data has been redacted.[/dim]")
4169
+ console.print("[white]Sensitive data has been redacted.[/white]")
3842
4170
  console.print(f"\n[bold]Send this file to Tweek support for analysis.[/bold]")
3843
4171
  except Exception as e:
3844
4172
  console.print(f"[red]Failed to create bundle: {e}[/red]")
@@ -3884,8 +4212,8 @@ def proxy_start(port: int, web_port: int, foreground: bool, log_only: bool):
3884
4212
 
3885
4213
  if not PROXY_AVAILABLE:
3886
4214
  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]")
4215
+ console.print(" [white]Hint: Install with: pip install tweek[proxy][/white]")
4216
+ console.print(" [white]This adds mitmproxy for HTTP(S) interception.[/white]")
3889
4217
  return
3890
4218
 
3891
4219
  from tweek.proxy.server import start_proxy
@@ -3906,7 +4234,7 @@ def proxy_start(port: int, web_port: int, foreground: bool, log_only: bool):
3906
4234
  console.print(f" export HTTPS_PROXY=http://127.0.0.1:{port}")
3907
4235
  console.print(f" export HTTP_PROXY=http://127.0.0.1:{port}")
3908
4236
  console.print()
3909
- console.print("[dim]Or use 'tweek proxy wrap' to create a wrapper script[/dim]")
4237
+ console.print("[white]Or use 'tweek proxy wrap' to create a wrapper script[/white]")
3910
4238
  else:
3911
4239
  console.print(f"[red]✗[/red] {message}")
3912
4240
 
@@ -3951,7 +4279,7 @@ def proxy_trust():
3951
4279
 
3952
4280
  if not PROXY_AVAILABLE:
3953
4281
  console.print("[red]✗[/red] Proxy dependencies not installed.")
3954
- console.print("[dim]Run: pip install tweek\\[proxy][/dim]")
4282
+ console.print("[white]Run: pip install tweek\\[proxy][/white]")
3955
4283
  return
3956
4284
 
3957
4285
  from tweek.proxy.server import install_ca_certificate, get_proxy_info
@@ -3963,11 +4291,11 @@ def proxy_trust():
3963
4291
  console.print("This will install a local CA certificate to enable HTTPS interception.")
3964
4292
  console.print("The certificate is generated on YOUR machine and never transmitted.")
3965
4293
  console.print()
3966
- console.print(f"[dim]Certificate location: {info['ca_cert']}[/dim]")
4294
+ console.print(f"[white]Certificate location: {info['ca_cert']}[/white]")
3967
4295
  console.print()
3968
4296
 
3969
4297
  if not click.confirm("Install certificate? (requires admin password)"):
3970
- console.print("[dim]Cancelled[/dim]")
4298
+ console.print("[white]Cancelled[/white]")
3971
4299
  return
3972
4300
 
3973
4301
  success, message = install_ca_certificate()
@@ -4019,7 +4347,7 @@ def proxy_config(set_enabled, set_disabled, port):
4019
4347
  yaml.dump(config, f, default_flow_style=False)
4020
4348
 
4021
4349
  console.print(f"[green]✓[/green] Proxy mode enabled (port {port})")
4022
- console.print("[dim]Run 'tweek proxy start' to start the proxy[/dim]")
4350
+ console.print("[white]Run 'tweek proxy start' to start the proxy[/white]")
4023
4351
 
4024
4352
  elif set_disabled:
4025
4353
  if "proxy" in config:
@@ -4061,10 +4389,10 @@ def proxy_wrap(app_name: str, command: str, output: str, port: int):
4061
4389
  console.print(f" chmod +x {output_path}")
4062
4390
  console.print(f" ./{output_path.name}")
4063
4391
  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]")
4392
+ console.print("[white]The script will:[/white]")
4393
+ console.print("[white] 1. Start Tweek proxy if not running[/white]")
4394
+ console.print("[white] 2. Set proxy environment variables[/white]")
4395
+ console.print(f"[white] 3. Run: {command}[/white]")
4068
4396
 
4069
4397
 
4070
4398
  @proxy.command("setup",
@@ -4139,9 +4467,9 @@ def proxy_setup():
4139
4467
  print_warning("Certificate module not available. Run: tweek proxy trust")
4140
4468
  except Exception as e:
4141
4469
  print_warning(f"Could not set up certificate: {e}")
4142
- console.print(" [dim]You can do this later with: tweek proxy trust[/dim]")
4470
+ console.print(" [white]You can do this later with: tweek proxy trust[/white]")
4143
4471
  else:
4144
- console.print(" [dim]Skipped. Run 'tweek proxy trust' later.[/dim]")
4472
+ console.print(" [white]Skipped. Run 'tweek proxy trust' later.[/white]")
4145
4473
  console.print()
4146
4474
 
4147
4475
  # Step 3: Shell environment
@@ -4165,13 +4493,13 @@ def proxy_setup():
4165
4493
  f.write(f"export HTTP_PROXY=http://127.0.0.1:{port}\n")
4166
4494
  f.write(f"export HTTPS_PROXY=http://127.0.0.1:{port}\n")
4167
4495
  print_success(f"Added to {shell_rc}")
4168
- console.print(f" [dim]Restart your shell or run: source {shell_rc}[/dim]")
4496
+ console.print(f" [white]Restart your shell or run: source {shell_rc}[/white]")
4169
4497
  except Exception as e:
4170
4498
  print_warning(f"Could not write to {shell_rc}: {e}")
4171
4499
  else:
4172
- console.print(" [dim]Skipped. Set HTTP_PROXY and HTTPS_PROXY manually.[/dim]")
4500
+ console.print(" [white]Skipped. Set HTTP_PROXY and HTTPS_PROXY manually.[/white]")
4173
4501
  else:
4174
- console.print(" [dim]Could not detect shell config file.[/dim]")
4502
+ console.print(" [white]Could not detect shell config file.[/white]")
4175
4503
  console.print(f" Add these to your shell profile:")
4176
4504
  console.print(f" export HTTP_PROXY=http://127.0.0.1:{port}")
4177
4505
  console.print(f" export HTTPS_PROXY=http://127.0.0.1:{port}")
@@ -4265,7 +4593,7 @@ def plugins_list(category: str, show_all: bool):
4265
4593
  license_style = "green" if license_tier == LicenseTier.FREE else "cyan"
4266
4594
 
4267
4595
  source_str = info.source.value if hasattr(info, 'source') else "builtin"
4268
- source_style = "blue" if source_str == "git" else "dim"
4596
+ source_style = "blue" if source_str == "git" else "white"
4269
4597
 
4270
4598
  table.add_row(
4271
4599
  info.name,
@@ -4279,6 +4607,18 @@ def plugins_list(category: str, show_all: bool):
4279
4607
  console.print(table)
4280
4608
  console.print()
4281
4609
 
4610
+ # Summary line across all categories
4611
+ total_count = 0
4612
+ enabled_count = 0
4613
+ for cat in list(PluginCategory):
4614
+ for info in registry.list_plugins(cat):
4615
+ total_count += 1
4616
+ if info.enabled:
4617
+ enabled_count += 1
4618
+ disabled_count = total_count - enabled_count
4619
+ console.print(f"Plugins: {total_count} registered, {enabled_count} enabled, {disabled_count} disabled")
4620
+ console.print()
4621
+
4282
4622
  except ImportError as e:
4283
4623
  console.print(f"[red]Plugin system not available: {e}[/red]")
4284
4624
 
@@ -4335,7 +4675,7 @@ def plugins_info(plugin_name: str, category: str):
4335
4675
  plugin_cfg = cfg.get_plugin_config(found_cat, plugin_name)
4336
4676
 
4337
4677
  console.print(f"\n[bold]{found_info.name}[/bold] ({found_cat})")
4338
- console.print(f"[dim]{found_info.metadata.description}[/dim]")
4678
+ console.print(f"[white]{found_info.metadata.description}[/white]")
4339
4679
  console.print()
4340
4680
 
4341
4681
  table = Table(show_header=False)
@@ -4507,7 +4847,7 @@ def plugins_scan(content: str, direction: str, plugin: str):
4507
4847
 
4508
4848
  if not plugins_to_use:
4509
4849
  console.print("[yellow]No compliance plugins enabled.[/yellow]")
4510
- console.print("[dim]Enable plugins with: tweek plugins enable <name> -c compliance[/dim]")
4850
+ console.print("[white]Enable plugins with: tweek plugins enable <name> -c compliance[/white]")
4511
4851
  return
4512
4852
 
4513
4853
  for p in plugins_to_use:
@@ -4521,12 +4861,12 @@ def plugins_scan(content: str, direction: str, plugin: str):
4521
4861
  "critical": "red bold",
4522
4862
  "high": "red",
4523
4863
  "medium": "yellow",
4524
- "low": "dim",
4864
+ "low": "white",
4525
4865
  }
4526
4866
  style = severity_styles.get(finding.severity.value, "white")
4527
4867
 
4528
4868
  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]")
4869
+ console.print(f" [white]Matched: {finding.matched_text[:60]}{'...' if len(finding.matched_text) > 60 else ''}[/white]")
4530
4870
  if finding.description:
4531
4871
  console.print(f" {finding.description}")
4532
4872
 
@@ -4600,11 +4940,11 @@ def plugins_install(name: str, version: str, from_lockfile: bool, no_verify: boo
4600
4940
  console.print(f"[green]\u2713[/green] {msg}")
4601
4941
  else:
4602
4942
  console.print(f"[red]\u2717[/red] {msg}")
4603
- console.print(f" [dim]Hint: Check network connectivity or try: tweek plugins registry --refresh[/dim]")
4943
+ console.print(f" [white]Hint: Check network connectivity or try: tweek plugins registry --refresh[/white]")
4604
4944
 
4605
4945
  except Exception as e:
4606
4946
  console.print(f"[red]Error: {e}[/red]")
4607
- console.print(f" [dim]Hint: Check network connectivity and try again[/dim]")
4947
+ console.print(f" [white]Hint: Check network connectivity and try again[/white]")
4608
4948
 
4609
4949
 
4610
4950
  @plugins.command("update",
@@ -4972,87 +5312,6 @@ def serve():
4972
5312
  console.print(f"[red]MCP server error: {e}[/red]")
4973
5313
 
4974
5314
 
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
5315
  # =============================================================================
5057
5316
  # MCP PROXY COMMANDS
5058
5317
  # =============================================================================
@@ -5208,13 +5467,13 @@ def chamber_list():
5208
5467
  items = chamber.list_chamber()
5209
5468
 
5210
5469
  if not items:
5211
- console.print("[dim]Chamber is empty.[/dim]")
5470
+ console.print("[white]Chamber is empty.[/white]")
5212
5471
  return
5213
5472
 
5214
5473
  table = Table(title="Isolation Chamber")
5215
5474
  table.add_column("Name", style="cyan")
5216
5475
  table.add_column("Has SKILL.md", style="green")
5217
- table.add_column("Path", style="dim")
5476
+ table.add_column("Path", style="white")
5218
5477
 
5219
5478
  for item in items:
5220
5479
  has_md = "Yes" if item["has_skill_md"] else "[red]No[/red]"
@@ -5309,7 +5568,7 @@ def jail_list():
5309
5568
  items = chamber.list_jail()
5310
5569
 
5311
5570
  if not items:
5312
- console.print("[dim]Jail is empty.[/dim]")
5571
+ console.print("[white]Jail is empty.[/white]")
5313
5572
  return
5314
5573
 
5315
5574
  table = Table(title="Skill Jail")
@@ -5381,7 +5640,7 @@ def skills_report(name: str):
5381
5640
  report_data = chamber.get_report(name)
5382
5641
 
5383
5642
  if not report_data:
5384
- console.print(f"[dim]No report found for '{name}'.[/dim]")
5643
+ console.print(f"[white]No report found for '{name}'.[/white]")
5385
5644
  return
5386
5645
 
5387
5646
  console.print(Panel(
@@ -5503,7 +5762,7 @@ def sandbox_status():
5503
5762
  else:
5504
5763
  console.print(f"[bold]Project:[/bold] {project_dir}")
5505
5764
  console.print(f"[bold]Layer:[/bold] 0-1 (no project isolation)")
5506
- console.print("[dim]Run 'tweek sandbox init' to enable project isolation.[/dim]")
5765
+ console.print("[white]Run 'tweek sandbox init' to enable project isolation.[/white]")
5507
5766
 
5508
5767
 
5509
5768
  @sandbox.command("init")
@@ -5577,7 +5836,7 @@ def sandbox_list():
5577
5836
  projects = registry.list_projects()
5578
5837
 
5579
5838
  if not projects:
5580
- console.print("[dim]No projects registered. Run 'tweek sandbox init' in a project.[/dim]")
5839
+ console.print("[white]No projects registered. Run 'tweek sandbox init' in a project.[/white]")
5581
5840
  return
5582
5841
 
5583
5842
  table = Table(title="Registered Projects")
@@ -5649,12 +5908,12 @@ def sandbox_logs(show_global: bool, limit: int):
5649
5908
 
5650
5909
  events = logger.get_recent_events(limit=limit)
5651
5910
  if not events:
5652
- console.print("[dim]No events found.[/dim]")
5911
+ console.print("[white]No events found.[/white]")
5653
5912
  return
5654
5913
 
5655
5914
  from rich.table import Table
5656
5915
  table = Table()
5657
- table.add_column("Time", style="dim")
5916
+ table.add_column("Time", style="white")
5658
5917
  table.add_column("Type")
5659
5918
  table.add_column("Tool")
5660
5919
  table.add_column("Decision", style="green")
@@ -5734,7 +5993,7 @@ def sandbox_verify():
5734
5993
  checks_passed += 1
5735
5994
  else:
5736
5995
  console.print(" Sandbox initialized: [red]NO[/red]")
5737
- console.print(" [dim]Run 'tweek sandbox init' to enable.[/dim]")
5996
+ console.print(" [white]Run 'tweek sandbox init' to enable.[/white]")
5738
5997
 
5739
5998
  # Check 3: Layer
5740
5999
  checks_total += 1
@@ -5755,7 +6014,7 @@ def sandbox_verify():
5755
6014
  elif sandbox:
5756
6015
  console.print(" Project security.db: [yellow]NOT FOUND[/yellow]")
5757
6016
  else:
5758
- console.print(" Project security.db: [dim]N/A (sandbox inactive)[/dim]")
6017
+ console.print(" Project security.db: [white]N/A (sandbox inactive)[/white]")
5759
6018
 
5760
6019
  # Check 5: .gitignore
5761
6020
  checks_total += 1
@@ -5785,7 +6044,7 @@ def docker_init():
5785
6044
  bridge = DockerBridge()
5786
6045
  if not bridge.is_docker_available():
5787
6046
  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]")
6047
+ console.print("[white]Install Docker Desktop from https://www.docker.com/products/docker-desktop/[/white]")
5789
6048
  raise SystemExit(1)
5790
6049
 
5791
6050
  from tweek.sandbox.project import _detect_project_dir
@@ -5796,7 +6055,7 @@ def docker_init():
5796
6055
 
5797
6056
  compose_path = bridge.init(project_dir)
5798
6057
  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]")
6058
+ console.print("[white]Run 'tweek sandbox docker run' to start the container.[/white]")
5800
6059
 
5801
6060
 
5802
6061
  @sandbox_docker.command("run")
@@ -5833,7 +6092,7 @@ def docker_status():
5833
6092
  compose = project_dir / ".tweek" / "docker-compose.yaml"
5834
6093
  console.print(f"[bold]Docker config:[/bold] {'exists' if compose.exists() else 'not generated'}")
5835
6094
  else:
5836
- console.print("[dim]Not in a project directory.[/dim]")
6095
+ console.print("[white]Not in a project directory.[/white]")
5837
6096
 
5838
6097
 
5839
6098
  # =========================================================================
@@ -5894,7 +6153,7 @@ def override_create(pattern: str, mode: str, duration_minutes: Optional[int], re
5894
6153
  if reason:
5895
6154
  console.print(f" Reason: {reason}")
5896
6155
  console.print()
5897
- console.print("[dim]Next time this pattern triggers, you'll see an 'ask' prompt instead of a hard block.[/dim]")
6156
+ console.print("[white]Next time this pattern triggers, you'll see an 'ask' prompt instead of a hard block.[/white]")
5898
6157
 
5899
6158
 
5900
6159
  @override_group.command("list")
@@ -5907,7 +6166,7 @@ def override_list():
5907
6166
  active_patterns = {o["pattern"] for o in active}
5908
6167
 
5909
6168
  if not all_overrides:
5910
- console.print("[dim]No break-glass overrides found.[/dim]")
6169
+ console.print("[white]No break-glass overrides found.[/white]")
5911
6170
  return
5912
6171
 
5913
6172
  table = Table(title="Break-Glass Overrides")
@@ -5921,9 +6180,9 @@ def override_list():
5921
6180
  if o["pattern"] in active_patterns and not o.get("used"):
5922
6181
  status = "[green]active[/green]"
5923
6182
  elif o.get("used"):
5924
- status = "[dim]consumed[/dim]"
6183
+ status = "[white]consumed[/white]"
5925
6184
  else:
5926
- status = "[dim]expired[/dim]"
6185
+ status = "[white]expired[/white]"
5927
6186
 
5928
6187
  table.add_row(
5929
6188
  o["pattern"],
@@ -6012,7 +6271,7 @@ def feedback_stats(above_threshold: bool):
6012
6271
 
6013
6272
  stats = get_stats()
6014
6273
  if not stats:
6015
- console.print("[dim]No feedback data recorded yet.[/dim]")
6274
+ console.print("[white]No feedback data recorded yet.[/white]")
6016
6275
  return
6017
6276
 
6018
6277
  table = Table(title="Pattern FP Statistics")
@@ -6053,7 +6312,7 @@ def feedback_reset(pattern_name: str):
6053
6312
  if result.get("was_demoted"):
6054
6313
  console.print(f" Restored severity: {result.get('original_severity')}")
6055
6314
  else:
6056
- console.print(f"[dim]No feedback data found for '{pattern_name}'.[/dim]")
6315
+ console.print(f"[white]No feedback data found for '{pattern_name}'.[/white]")
6057
6316
 
6058
6317
 
6059
6318
  # =========================================================================
@@ -6094,7 +6353,7 @@ def memory_status():
6094
6353
  if last_decay:
6095
6354
  console.print(f" Last decay: {last_decay}")
6096
6355
  else:
6097
- console.print(" Last decay: [dim]never[/dim]")
6356
+ console.print(" Last decay: [white]never[/white]")
6098
6357
 
6099
6358
  db_size = stats.get("db_size_bytes", 0)
6100
6359
  if db_size > 1024 * 1024:
@@ -6118,7 +6377,7 @@ def memory_patterns(min_decisions: int, sort_by: str):
6118
6377
  patterns = store.get_pattern_stats(min_decisions=min_decisions, sort_by=sort_by)
6119
6378
 
6120
6379
  if not patterns:
6121
- console.print("[dim]No pattern decision data recorded yet.[/dim]")
6380
+ console.print("[white]No pattern decision data recorded yet.[/white]")
6122
6381
  return
6123
6382
 
6124
6383
  table = Table(title="Pattern Decision History")
@@ -6135,7 +6394,7 @@ def memory_patterns(min_decisions: int, sort_by: str):
6135
6394
  ratio_style = "green" if ratio >= 0.9 else ("yellow" if ratio >= 0.5 else "red")
6136
6395
  table.add_row(
6137
6396
  p.get("pattern_name", "?"),
6138
- p.get("path_prefix") or "[dim]-[/dim]",
6397
+ p.get("path_prefix") or "[white]-[/white]",
6139
6398
  str(p.get("total_decisions", 0)),
6140
6399
  f"{p.get('weighted_approvals', 0):.1f}",
6141
6400
  f"{p.get('weighted_denials', 0):.1f}",
@@ -6156,7 +6415,7 @@ def memory_sources(suspicious: bool):
6156
6415
  sources = store.get_all_sources(suspicious_only=suspicious)
6157
6416
 
6158
6417
  if not sources:
6159
- console.print("[dim]No source trust data recorded yet.[/dim]")
6418
+ console.print("[white]No source trust data recorded yet.[/white]")
6160
6419
  return
6161
6420
 
6162
6421
  table = Table(title="Source Trust Scores")
@@ -6191,7 +6450,7 @@ def memory_suggestions(show_all: bool):
6191
6450
  suggestions = store.get_whitelist_suggestions(pending_only=not show_all)
6192
6451
 
6193
6452
  if not suggestions:
6194
- console.print("[dim]No whitelist suggestions available.[/dim]")
6453
+ console.print("[white]No whitelist suggestions available.[/white]")
6195
6454
  return
6196
6455
 
6197
6456
  table = Table(title="Learned Whitelist Suggestions")
@@ -6209,8 +6468,8 @@ def memory_suggestions(show_all: bool):
6209
6468
  table.add_row(
6210
6469
  str(s.id),
6211
6470
  s.pattern_name,
6212
- s.tool_name or "[dim]-[/dim]",
6213
- s.path_prefix or "[dim]-[/dim]",
6471
+ s.tool_name or "[white]-[/white]",
6472
+ s.path_prefix or "[white]-[/white]",
6214
6473
  str(s.approval_count),
6215
6474
  str(s.denial_count),
6216
6475
  f"{s.confidence:.0%}",
@@ -6229,7 +6488,7 @@ def memory_accept(suggestion_id: int):
6229
6488
  store = get_memory_store()
6230
6489
  if store.review_whitelist_suggestion(suggestion_id, accepted=True):
6231
6490
  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]")
6491
+ console.print(" [white]Note: To apply to overrides.yaml, manually add the whitelist rule.[/white]")
6233
6492
  else:
6234
6493
  console.print(f"[red]Suggestion #{suggestion_id} not found.[/red]")
6235
6494
 
@@ -6260,7 +6519,7 @@ def memory_baseline(project_hash: Optional[str]):
6260
6519
  baselines = store.get_workflow_baseline(project_hash)
6261
6520
 
6262
6521
  if not baselines:
6263
- console.print("[dim]No workflow baseline data for this project.[/dim]")
6522
+ console.print("[white]No workflow baseline data for this project.[/white]")
6264
6523
  return
6265
6524
 
6266
6525
  table = Table(title=f"Workflow Baseline (project: {project_hash[:8]}...)")
@@ -6276,7 +6535,7 @@ def memory_baseline(project_hash: Optional[str]):
6276
6535
  pct_style = "green" if denial_pct < 0.1 else ("yellow" if denial_pct < 0.3 else "red")
6277
6536
  table.add_row(
6278
6537
  b.tool_name,
6279
- str(b.hour_of_day) if b.hour_of_day is not None else "[dim]-[/dim]",
6538
+ str(b.hour_of_day) if b.hour_of_day is not None else "[white]-[/white]",
6280
6539
  str(b.invocation_count),
6281
6540
  str(b.denied_count),
6282
6541
  f"[{pct_style}]{denial_pct:.0%}[/{pct_style}]",
@@ -6295,7 +6554,7 @@ def memory_audit(limit: int):
6295
6554
  entries = store.get_audit_log(limit=limit)
6296
6555
 
6297
6556
  if not entries:
6298
- console.print("[dim]No audit entries.[/dim]")
6557
+ console.print("[white]No audit entries.[/white]")
6299
6558
  return
6300
6559
 
6301
6560
  table = Table(title=f"Memory Audit Log (last {limit})")
@@ -6337,7 +6596,7 @@ def memory_clear(table_name: Optional[str], confirm: bool):
6337
6596
  if not confirm:
6338
6597
  target = table_name or "ALL"
6339
6598
  if not click.confirm(f"Clear {target} memory data? This cannot be undone"):
6340
- console.print("[dim]Cancelled.[/dim]")
6599
+ console.print("[white]Cancelled.[/white]")
6341
6600
  return
6342
6601
 
6343
6602
  store = get_memory_store()