codemie-test-harness 0.1.169__py3-none-any.whl → 0.1.170__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.

Potentially problematic release.


This version of codemie-test-harness might be problematic. Click here for more details.

@@ -1,48 +1,638 @@
1
1
  from __future__ import annotations
2
2
  import click
3
- from ..constants import CONSOLE
4
- from ..utils import load_config, get_config_value, set_config_value
3
+ from rich.table import Table
4
+ from typing import Dict, List
5
+ from ..constants import (
6
+ CONSOLE,
7
+ CREDENTIAL_CATEGORIES,
8
+ INTEGRATION_KEYS,
9
+ mask_sensitive_value,
10
+ is_sensitive_key,
11
+ )
12
+ from ..utils import (
13
+ load_config,
14
+ get_config_value,
15
+ set_config_value,
16
+ save_config,
17
+ unset_config_key,
18
+ )
19
+
20
+
21
+ # Helper functions (moved from integrations_manager.py)
22
+ def _get_integration_category(key: str) -> str:
23
+ """Get the integration category name for a configuration key."""
24
+ for category_id, category_info in CREDENTIAL_CATEGORIES.items():
25
+ if key in category_info["keys"]:
26
+ return category_info["name"]
27
+ return "Unknown"
28
+
29
+
30
+ def _show_integration_configs(category: str = None, show_real: bool = False) -> None:
31
+ """Display current integration configurations in a formatted table."""
32
+ config = load_config()
33
+ if not config:
34
+ CONSOLE.print("[yellow]No integration configurations found[/]")
35
+ return
36
+
37
+ # Filter configurations based on category parameter
38
+ if category:
39
+ if category in CREDENTIAL_CATEGORIES:
40
+ keys_to_show = CREDENTIAL_CATEGORIES[category]["keys"]
41
+ filtered_config = {k: v for k, v in config.items() if k in keys_to_show}
42
+ else:
43
+ CONSOLE.print(f"[red]Unknown integration category: {category}[/]")
44
+ return
45
+ else:
46
+ # Show all integration configurations
47
+ filtered_config = {k: v for k, v in config.items() if k in INTEGRATION_KEYS}
48
+
49
+ if not filtered_config:
50
+ CONSOLE.print(
51
+ "[yellow]No integration configurations found for the specified criteria[/]"
52
+ )
53
+ return
54
+
55
+ # Create formatted table for display
56
+ title = "Integration Configurations" + (
57
+ " (Real Values)" if show_real else " (Masked Values)"
58
+ )
59
+ table = Table(title=title)
60
+ table.add_column("Configuration Key", style="cyan")
61
+ table.add_column("Value", style="green")
62
+ table.add_column("Integration Category", style="blue")
63
+
64
+ for key, value in sorted(filtered_config.items()):
65
+ display_value = (
66
+ mask_sensitive_value(str(value), show_real=show_real)
67
+ if is_sensitive_key(key)
68
+ else str(value)
69
+ )
70
+ category_name = _get_integration_category(key)
71
+ table.add_row(key, display_value, category_name)
72
+
73
+ CONSOLE.print(table)
74
+
75
+
76
+ def _list_integration_categories() -> None:
77
+ """Display all available integration categories in a formatted table."""
78
+ table = Table(title="Available Integration Categories")
79
+ table.add_column("Category ID", style="cyan")
80
+ table.add_column("Category Name", style="green")
81
+ table.add_column("Description", style="blue")
82
+ table.add_column("Config Keys", style="dim", justify="right")
83
+
84
+ for category_id, category_info in CREDENTIAL_CATEGORIES.items():
85
+ table.add_row(
86
+ category_id,
87
+ category_info["name"],
88
+ category_info["description"],
89
+ str(len(category_info["keys"])),
90
+ )
91
+
92
+ CONSOLE.print(table)
93
+
94
+
95
+ def _validate_integrations() -> Dict[str, Dict[str, List[str]]]:
96
+ """Validate all configured integrations and return detailed status."""
97
+ config = load_config()
98
+ validation_results = {}
99
+
100
+ for category_id, category_info in CREDENTIAL_CATEGORIES.items():
101
+ configured_keys = []
102
+ missing_keys = []
103
+
104
+ for config_key in category_info["keys"]:
105
+ if config and config_key in config and config[config_key]:
106
+ configured_keys.append(config_key)
107
+ else:
108
+ missing_keys.append(config_key)
109
+
110
+ validation_results[category_info["name"]] = {
111
+ "configured": configured_keys,
112
+ "missing": missing_keys,
113
+ }
114
+
115
+ return validation_results
116
+
117
+
118
+ def _setup_integration_category(category: str) -> None:
119
+ """Interactively configure all settings for a specific integration category."""
120
+ if category not in CREDENTIAL_CATEGORIES:
121
+ CONSOLE.print(f"[red]Unknown integration category: {category}[/]")
122
+ return
123
+
124
+ category_info = CREDENTIAL_CATEGORIES[category]
125
+ CONSOLE.print(f"\n=== {category_info['name']} Configuration Setup ===")
126
+ CONSOLE.print(f"Description: {category_info['description']}\n")
127
+ CONSOLE.print("[dim]Press Enter to skip a setting, or Ctrl+C to cancel setup[/]\n")
128
+
129
+ config = load_config()
130
+ updated_count = 0
131
+
132
+ for config_key in category_info["keys"]:
133
+ current_value = config.get(config_key, "")
134
+
135
+ # Show current value (masked if sensitive)
136
+ current_display = ""
137
+ if current_value:
138
+ displayed_value = (
139
+ mask_sensitive_value(current_value)
140
+ if is_sensitive_key(config_key)
141
+ else current_value
142
+ )
143
+ current_display = f" (current: {displayed_value})"
144
+
145
+ prompt = f"{config_key}{current_display}: "
146
+
147
+ try:
148
+ user_input = input(prompt).strip()
149
+
150
+ if user_input:
151
+ set_config_value(config_key, user_input)
152
+ updated_count += 1
153
+ CONSOLE.print(f"[green]✓ Configured[/] {config_key}")
154
+ else:
155
+ CONSOLE.print(f"[yellow]⚠ Skipped[/] {config_key}")
156
+
157
+ except KeyboardInterrupt:
158
+ CONSOLE.print("\n[yellow]Configuration setup cancelled.[/]")
159
+ return
160
+
161
+ CONSOLE.print(
162
+ f"\n[green]Successfully updated {updated_count} integration setting(s)[/]"
163
+ )
164
+
165
+
166
+ def _setup_all_integration_categories() -> None:
167
+ """Interactively configure all available integration categories."""
168
+ CONSOLE.print("[cyan]Setting up all integration categories...\n[/]")
169
+
170
+ total_categories = len(CREDENTIAL_CATEGORIES)
171
+ current_category = 0
172
+
173
+ for category_id in CREDENTIAL_CATEGORIES.keys():
174
+ current_category += 1
175
+ category_name = CREDENTIAL_CATEGORIES[category_id]["name"]
176
+
177
+ CONSOLE.print(f"\n[dim]({current_category}/{total_categories})[/]")
178
+ proceed = input(f"Configure {category_name}? (y/N): ").strip().lower()
179
+
180
+ if proceed in ["y", "yes"]:
181
+ _setup_integration_category(category_id)
182
+ else:
183
+ CONSOLE.print(f"[yellow]⚠ Skipped {category_name}[/]")
184
+
185
+ CONSOLE.print("\n[green]🎉 All integration categories setup completed![/]")
5
186
 
6
187
 
7
188
  @click.group(name="config")
8
189
  def config_cmd():
9
- """Manage configuration for test harness.
190
+ """Manage configuration and credentials for test harness.
10
191
 
11
- Keys:
12
- - AUTH_SERVER_URL, AUTH_CLIENT_ID, AUTH_CLIENT_SECRET, AUTH_REALM_NAME, CODEMIE_API_DOMAIN
13
- - PYTEST_MARKS, PYTEST_N, PYTEST_RERUNS
14
- - GIT_ENV
15
- GitLab: GITLAB_URL, GITLAB_TOKEN, GITLAB_PROJECT, GITLAB_PROJECT_ID
16
- GitHub: GITHUB_URL, GITHUB_TOKEN, GITHUB_PROJECT
17
- Jira: JIRA_URL, JIRA_TOKEN, JQL
18
- Confluence: CONFLUENCE_URL, CONFLUENCE_TOKEN, CQL
192
+ Supports setting individual credentials, category-based interactive setup,
193
+ validation, and more.
194
+
195
+ Categories:
196
+ - version-control: GitLab, GitHub
197
+ - project-management: JIRA, Confluence
198
+ - cloud-providers: AWS, Azure, GCP
199
+ - development-tools: Azure DevOps, ServiceNow, Keycloak, SonarQube
200
+ - notifications: Email, OAuth, Telegram
201
+ - research-tools: Kubernetes, Report Portal, Elasticsearch
202
+ - data-management: LiteLLM, SQL databases
19
203
  """
20
204
  pass
21
205
 
22
206
 
23
207
  @config_cmd.command(name="list")
24
- def config_list():
208
+ @click.option(
209
+ "--show-values",
210
+ is_flag=True,
211
+ help="Show real values instead of masked values (use with caution)",
212
+ )
213
+ def config_list(show_values: bool = False):
214
+ """List all configuration values.
215
+
216
+ Examples:
217
+ codemie-test-harness config list
218
+ codemie-test-harness config list --show-values
219
+ """
25
220
  cfg = load_config()
26
221
  if not cfg:
27
222
  CONSOLE.print("[yellow]No config set yet[/]")
28
223
  else:
224
+ title_suffix = " (Real Values)" if show_values else " (Masked Values)"
225
+ CONSOLE.print(f"[bold cyan]Configuration{title_suffix}[/bold cyan]")
29
226
  for k, v in cfg.items():
30
- CONSOLE.print(f"[cyan]{k}[/] = [green]{v}[/]")
227
+ display_value = (
228
+ mask_sensitive_value(str(v), show_real=show_values)
229
+ if is_sensitive_key(k)
230
+ else str(v)
231
+ )
232
+ CONSOLE.print(f"[cyan]{k}[/] = [green]{display_value}[/]")
31
233
 
32
234
 
33
235
  @config_cmd.command(name="set")
34
- @click.argument("key")
35
- @click.argument("value")
36
- def config_set(key: str, value: str):
37
- set_config_value(key, value)
38
- CONSOLE.print(f"[green]Saved[/] {key} = {value}")
236
+ @click.argument("key", required=False)
237
+ @click.argument("value", required=False)
238
+ def config_set(key: str = None, value: str = None):
239
+ """Set configuration values using key-value pairs.
240
+
241
+ Supports all 86+ environment variables across 10 categories.
242
+
243
+ Examples:
244
+ codemie-test-harness config set GITLAB_TOKEN glpat-xxx
245
+ codemie-test-harness config set SONAR_URL https://sonar.example.com
246
+ codemie-test-harness config set KUBERNETES_TOKEN k8s-token-123
247
+ """
248
+ # If key-value pair provided
249
+ if key and value:
250
+ set_config_value(key, value)
251
+ display_value = mask_sensitive_value(value) if is_sensitive_key(key) else value
252
+ CONSOLE.print(f"[green]Saved[/] {key} = {display_value}")
253
+ else:
254
+ CONSOLE.print(
255
+ "[yellow]Please provide key and value: codemie-test-harness config set KEY VALUE[/yellow]"
256
+ )
257
+ CONSOLE.print("\n[cyan]Examples:[/cyan]")
258
+ CONSOLE.print(" codemie-test-harness config set GITLAB_TOKEN glpat-xxx")
259
+ CONSOLE.print(
260
+ " codemie-test-harness config set SONAR_URL https://sonar.example.com"
261
+ )
262
+ CONSOLE.print(
263
+ " codemie-test-harness config set AWS_ACCESS_KEY_ID AKIA1234567890"
264
+ )
265
+ CONSOLE.print(
266
+ "\n[dim]💡 Use 'codemie-test-harness config vars' to see all available variables[/dim]"
267
+ )
39
268
 
40
269
 
41
270
  @config_cmd.command(name="get")
42
271
  @click.argument("key")
43
- def config_get(key: str):
272
+ @click.option(
273
+ "--show-value",
274
+ is_flag=True,
275
+ help="Show real value instead of masked value (use with caution)",
276
+ )
277
+ def config_get(key: str, show_value: bool = False):
278
+ """Get a configuration value.
279
+
280
+ Examples:
281
+ codemie-test-harness config get GITLAB_TOKEN
282
+ codemie-test-harness config get GITLAB_TOKEN --show-value
283
+ """
44
284
  val = get_config_value(key)
45
285
  if val is None:
46
286
  CONSOLE.print(f"[yellow]{key} not set[/]")
47
287
  else:
48
- CONSOLE.print(f"{key} = {val}")
288
+ display_value = (
289
+ mask_sensitive_value(val, show_real=show_value)
290
+ if is_sensitive_key(key)
291
+ else val
292
+ )
293
+ CONSOLE.print(f"{key} = {display_value}")
294
+
295
+
296
+ @config_cmd.command(name="integrations")
297
+ @click.option(
298
+ "--category",
299
+ type=click.Choice(list(CREDENTIAL_CATEGORIES.keys())),
300
+ help="Show credentials for specific category",
301
+ )
302
+ @click.option(
303
+ "--show-values",
304
+ is_flag=True,
305
+ help="Show real values instead of masked values (use with caution)",
306
+ )
307
+ def config_integrations(category: str = None, show_values: bool = False):
308
+ """Show current credentials.
309
+
310
+ Examples:
311
+ codemie-test-harness config integrations
312
+ codemie-test-harness config integrations --category version-control
313
+ codemie-test-harness config integrations --show-values
314
+ codemie-test-harness config integrations --category cloud --show-values
315
+ """
316
+ _show_integration_configs(category=category, show_real=show_values)
317
+
318
+
319
+ @config_cmd.command(name="unset")
320
+ @click.option("--keys", help="Comma-separated list of keys to unset (case-insensitive)")
321
+ @click.option(
322
+ "--category",
323
+ type=click.Choice(list(CREDENTIAL_CATEGORIES.keys())),
324
+ help="Category to unset all keys from",
325
+ )
326
+ def config_unset(keys: str = None, category: str = None):
327
+ """Unset (remove) configuration keys.
328
+
329
+ Supports case-insensitive key removal and category-based removal.
330
+
331
+ Examples:
332
+ codemie-test-harness config unset --keys GITLAB_TOKEN,gitlab_url
333
+ codemie-test-harness config unset --keys some_setting,DEBUG_LEVEL
334
+ codemie-test-harness config unset --category version-control
335
+ """
336
+ if not keys and not category:
337
+ CONSOLE.print("[yellow]Please specify --keys or --category[/yellow]")
338
+ return
339
+
340
+ config = load_config()
341
+ if not config:
342
+ CONSOLE.print("[yellow]No configuration found[/yellow]")
343
+ return
344
+
345
+ removed_count = 0
346
+
347
+ # Handle category removal
348
+ if category:
349
+ category_info = CREDENTIAL_CATEGORIES[category]
350
+ category_keys = category_info["keys"]
351
+
352
+ for key in category_keys:
353
+ if unset_config_key(key):
354
+ removed_count += 1
355
+
356
+ CONSOLE.print(
357
+ f"[green]category:{category_info['name']}: Removed {removed_count} keys[/green]"
358
+ )
359
+
360
+ # Handle specific key removal (case-insensitive)
361
+ if keys:
362
+ key_list = [k.strip() for k in keys.split(",")]
363
+ config = load_config() # Reload config in case category removal happened
364
+ config_keys_lower = {k.lower(): k for k in config.keys()}
365
+
366
+ for key in key_list:
367
+ key_lower = key.lower()
368
+ if key_lower in config_keys_lower:
369
+ actual_key = config_keys_lower[key_lower]
370
+ if unset_config_key(actual_key):
371
+ removed_count += 1
372
+ CONSOLE.print(f"[green]{key}: Removed (was: {actual_key})[/green]")
373
+ else:
374
+ CONSOLE.print(f"[yellow]{key}: Not found[/yellow]")
375
+
376
+ if removed_count > 0:
377
+ CONSOLE.print(f"[green]Total keys removed: {removed_count}[/green]")
378
+ else:
379
+ CONSOLE.print("[yellow]No keys were removed[/yellow]")
380
+
381
+
382
+ @config_cmd.command(name="setup")
383
+ @click.option(
384
+ "--category",
385
+ type=click.Choice(list(CREDENTIAL_CATEGORIES.keys())),
386
+ help="Setup specific category interactively",
387
+ )
388
+ @click.option(
389
+ "--all", "setup_all", is_flag=True, help="Setup all categories interactively"
390
+ )
391
+ def config_setup(category: str = None, setup_all: bool = False):
392
+ """Interactive credential setup.
393
+
394
+ Examples:
395
+ codemie-test-harness config setup --category version-control
396
+ codemie-test-harness config setup --category cloud-providers
397
+ codemie-test-harness config setup --all
398
+ """
399
+ if setup_all:
400
+ _setup_all_integration_categories()
401
+ elif category:
402
+ _setup_integration_category(category)
403
+ else:
404
+ CONSOLE.print("[yellow]Please specify --category or --all[/yellow]")
405
+ CONSOLE.print("\nAvailable categories:")
406
+ _list_integration_categories()
407
+
408
+
409
+ @config_cmd.command(name="validate")
410
+ @click.option(
411
+ "--category",
412
+ type=click.Choice(list(CREDENTIAL_CATEGORIES.keys())),
413
+ help="Validate specific category",
414
+ )
415
+ def config_validate(category: str = None):
416
+ """Validate configured credentials.
417
+
418
+ Examples:
419
+ codemie-test-harness config validate
420
+ codemie-test-harness config validate --category cloud-providers
421
+ """
422
+
423
+ validation_results = _validate_integrations()
424
+
425
+ # Display validation results
426
+ if not validation_results:
427
+ CONSOLE.print("[yellow]No credentials configured[/yellow]")
428
+ return
429
+
430
+ # Filter by category if specified
431
+ if category:
432
+ if category in CREDENTIAL_CATEGORIES:
433
+ category_name = CREDENTIAL_CATEGORIES[category]["name"]
434
+ if category_name in validation_results:
435
+ validation_results = {category_name: validation_results[category_name]}
436
+ else:
437
+ validation_results = {}
438
+ else:
439
+ CONSOLE.print(f"[red]Unknown category: {category}[/red]")
440
+ return
441
+
442
+ # Create table
443
+ table = Table(show_header=True, header_style="bold magenta")
444
+ table.add_column("Category", style="cyan", no_wrap=True, width=25)
445
+ table.add_column("Credential", style="white", width=35)
446
+ table.add_column("Status", justify="center", width=15)
447
+
448
+ total_configured = 0
449
+ total_missing = 0
450
+
451
+ # Sort categories for consistent display
452
+ sorted_categories = sorted(validation_results.items())
453
+
454
+ for i, (category_name, results) in enumerate(sorted_categories):
455
+ configured = results["configured"]
456
+ missing = results["missing"]
457
+
458
+ total_configured += len(configured)
459
+ total_missing += len(missing)
460
+
461
+ # Add configured credentials
462
+ for j, key in enumerate(sorted(configured)):
463
+ cat_display = category_name if j == 0 else ""
464
+ table.add_row(cat_display, key, "[green]✓ Configured[/green]")
465
+
466
+ # Add missing credentials
467
+ for j, key in enumerate(sorted(missing)):
468
+ cat_display = category_name if j == 0 and not configured else ""
469
+ table.add_row(cat_display, key, "[yellow]⚠ Missing[/yellow]")
470
+
471
+ # Add separator row between categories (except for last category)
472
+ if i < len(sorted_categories) - 1 and (configured or missing):
473
+ table.add_row("", "", "", style="dim")
474
+
475
+ # Display table
476
+ CONSOLE.print("\n[bold blue]Credential Validation Results[/bold blue]")
477
+ CONSOLE.print(table)
478
+
479
+ # Summary
480
+ CONSOLE.print("\n[bold]Summary:[/bold]")
481
+ CONSOLE.print(f" [green]Configured: {total_configured}[/green]")
482
+ CONSOLE.print(f" [yellow]Missing: {total_missing}[/yellow]")
483
+
484
+ if total_missing == 0 and total_configured > 0:
485
+ CONSOLE.print("\n[green]✅ All credentials are configured![/green]")
486
+ elif total_configured == 0:
487
+ CONSOLE.print(
488
+ "\n[yellow]⚠️ No credentials are configured. Use 'codemie-test-harness config setup' to get started.[/yellow]"
489
+ )
490
+ else:
491
+ CONSOLE.print(
492
+ "\n[yellow]⚠️ Some credentials are still missing. Use 'codemie-test-harness config setup' to configure them.[/yellow]"
493
+ )
494
+
495
+
496
+ @config_cmd.command(name="categories")
497
+ @click.option(
498
+ "--list-vars",
499
+ "-l",
500
+ is_flag=True,
501
+ help="Show environment variables for each category",
502
+ )
503
+ @click.option("--category", "-c", help="Show variables for specific category only")
504
+ def config_categories(list_vars: bool = False, category: str = None):
505
+ """List available credential categories and optionally their environment variables."""
506
+ from ..constants import CREDENTIAL_CATEGORIES
507
+ from rich.console import Console
508
+ from rich.table import Table
509
+
510
+ console = Console()
511
+
512
+ if category:
513
+ # Show variables for specific category
514
+ if category not in CREDENTIAL_CATEGORIES:
515
+ click.echo(
516
+ f"❌ Category '{category}' not found. Available categories: {', '.join(CREDENTIAL_CATEGORIES.keys())}"
517
+ )
518
+ return
519
+
520
+ cat_info = CREDENTIAL_CATEGORIES[category]
521
+ click.echo(f"\n=== {cat_info['name']} ===")
522
+ click.echo(f"{cat_info['description']}\n")
523
+
524
+ if list_vars or True: # Always show vars when specific category requested
525
+ click.echo("Environment Variables:")
526
+ for var in sorted(cat_info["keys"]):
527
+ click.echo(f" • {var}")
528
+ return
529
+
530
+ if list_vars:
531
+ # Show all categories with their variables
532
+ for cat_id, cat_info in CREDENTIAL_CATEGORIES.items():
533
+ click.echo(f"\n=== {cat_info['name']} ({cat_id}) ===")
534
+ click.echo(f"{cat_info['description']}")
535
+ click.echo(f"Environment Variables ({len(cat_info['keys'])} total):")
536
+ for var in sorted(cat_info["keys"]):
537
+ click.echo(f" • {var}")
538
+ click.echo()
539
+ else:
540
+ # Show summary table (existing functionality)
541
+ table = Table(title="Available Credential Categories")
542
+ table.add_column("Category", style="cyan")
543
+ table.add_column("Name", style="green")
544
+ table.add_column("Description", style="yellow")
545
+ table.add_column("Keys", justify="right", style="magenta")
546
+
547
+ for cat_id, cat_info in CREDENTIAL_CATEGORIES.items():
548
+ table.add_row(
549
+ cat_id,
550
+ cat_info["name"],
551
+ cat_info["description"],
552
+ str(len(cat_info["keys"])),
553
+ )
554
+
555
+ console.print(table)
556
+ click.echo(
557
+ "\n💡 Use 'codemie-test-harness config vars <category>' to see environment variables for a specific category."
558
+ )
559
+
560
+
561
+ @config_cmd.command(name="vars")
562
+ @click.argument("category", required=False)
563
+ def config_vars(category: str = None):
564
+ """List environment variables for a specific category or all categories."""
565
+ from ..constants import CREDENTIAL_CATEGORIES
566
+
567
+ if category:
568
+ # Show variables for specific category
569
+ if category not in CREDENTIAL_CATEGORIES:
570
+ click.echo(f"❌ Category '{category}' not found.")
571
+ click.echo(
572
+ f"Available categories: {', '.join(CREDENTIAL_CATEGORIES.keys())}"
573
+ )
574
+ return
575
+
576
+ cat_info = CREDENTIAL_CATEGORIES[category]
577
+ click.echo(f"\n=== {cat_info['name']} ===")
578
+ click.echo(f"{cat_info['description']}")
579
+ click.echo(f"\nEnvironment Variables ({len(cat_info['keys'])} total):")
580
+ for var in sorted(cat_info["keys"]):
581
+ click.echo(f" {var}")
582
+ else:
583
+ # Show all categories with their variables
584
+ click.echo("Environment Variables by Category:\n")
585
+ for cat_id, cat_info in CREDENTIAL_CATEGORIES.items():
586
+ click.echo(f"=== {cat_info['name']} ({cat_id}) ===")
587
+ click.echo(f"Variables ({len(cat_info['keys'])} total):")
588
+ for var in sorted(cat_info["keys"]):
589
+ click.echo(f" {var}")
590
+ click.echo()
591
+
592
+
593
+ @config_cmd.command(name="clear")
594
+ @click.option("--force", is_flag=True, help="Skip confirmation prompt")
595
+ def config_clear(force: bool = False):
596
+ """Clear ALL configuration including credentials and settings.
597
+
598
+ This will remove EVERYTHING from the configuration file. Use with caution!
599
+
600
+ Examples:
601
+ codemie-test-harness config clear
602
+ codemie-test-harness config clear --force
603
+ """
604
+ from ..utils import load_config
605
+ from ..constants import CONSOLE
606
+
607
+ # Load current config to check if there's anything to clear
608
+ current_config = load_config()
609
+ if not current_config:
610
+ CONSOLE.print("[yellow]No configuration found to clear.[/yellow]")
611
+ return
612
+
613
+ # Count all items that will be removed
614
+ total_count = len(current_config)
615
+
616
+ if not force:
617
+ CONSOLE.print(
618
+ f"[yellow]⚠️ WARNING: This will remove ALL {total_count} configuration items![/yellow]"
619
+ )
620
+ CONSOLE.print("[yellow]This action cannot be undone.[/yellow]")
621
+
622
+ # Show what will be cleared
623
+ CONSOLE.print("\n[cyan]Items that will be cleared:[/cyan]")
624
+ for key in sorted(current_config.keys()):
625
+ CONSOLE.print(f" • {key}")
626
+
627
+ confirm = click.confirm("\nAre you sure you want to clear ALL configuration?")
628
+ if not confirm:
629
+ CONSOLE.print("[green]Operation cancelled.[/green]")
630
+ return
631
+
632
+ # Clear everything
633
+ save_config({})
634
+
635
+ CONSOLE.print(
636
+ f"[green]✅ Successfully cleared ALL {total_count} configuration items.[/green]"
637
+ )
638
+ CONSOLE.print("[cyan]Configuration file is now empty.[/cyan]")