dtSpark 1.1.0a3__py3-none-any.whl → 1.1.0a7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. dtSpark/_version.txt +1 -1
  2. dtSpark/aws/authentication.py +1 -1
  3. dtSpark/aws/bedrock.py +238 -239
  4. dtSpark/aws/costs.py +9 -5
  5. dtSpark/aws/pricing.py +25 -21
  6. dtSpark/cli_interface.py +77 -68
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +114 -91
  9. dtSpark/core/context_compaction.py +241 -226
  10. dtSpark/daemon/__init__.py +36 -22
  11. dtSpark/daemon/action_monitor.py +46 -17
  12. dtSpark/daemon/daemon_app.py +126 -104
  13. dtSpark/daemon/daemon_manager.py +59 -23
  14. dtSpark/daemon/pid_file.py +3 -2
  15. dtSpark/database/autonomous_actions.py +3 -0
  16. dtSpark/database/credential_prompt.py +52 -54
  17. dtSpark/files/manager.py +6 -12
  18. dtSpark/limits/__init__.py +1 -1
  19. dtSpark/limits/tokens.py +2 -2
  20. dtSpark/llm/anthropic_direct.py +246 -141
  21. dtSpark/llm/ollama.py +3 -1
  22. dtSpark/mcp_integration/manager.py +4 -4
  23. dtSpark/mcp_integration/tool_selector.py +83 -77
  24. dtSpark/resources/config.yaml.template +11 -0
  25. dtSpark/safety/patterns.py +45 -46
  26. dtSpark/safety/prompt_inspector.py +8 -1
  27. dtSpark/scheduler/creation_tools.py +273 -181
  28. dtSpark/scheduler/executor.py +503 -221
  29. dtSpark/tools/builtin.py +70 -53
  30. dtSpark/web/endpoints/autonomous_actions.py +12 -9
  31. dtSpark/web/endpoints/chat.py +8 -6
  32. dtSpark/web/endpoints/conversations.py +18 -9
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +70 -5
  36. dtSpark/web/ssl_utils.py +3 -3
  37. dtSpark/web/static/css/dark-theme.css +8 -29
  38. dtSpark/web/static/js/chat.js +6 -8
  39. dtSpark/web/static/js/main.js +8 -8
  40. dtSpark/web/static/js/sse-client.js +130 -122
  41. dtSpark/web/templates/actions.html +5 -5
  42. dtSpark/web/templates/base.html +15 -0
  43. dtSpark/web/templates/chat.html +10 -10
  44. dtSpark/web/templates/conversations.html +6 -2
  45. dtSpark/web/templates/goodbye.html +2 -2
  46. dtSpark/web/templates/main_menu.html +19 -17
  47. dtSpark/web/web_interface.py +2 -2
  48. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/METADATA +9 -2
  49. dtspark-1.1.0a7.dist-info/RECORD +96 -0
  50. dtspark-1.1.0a3.dist-info/RECORD +0 -96
  51. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/WHEEL +0 -0
  52. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/entry_points.txt +0 -0
  53. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/licenses/LICENSE +0 -0
  54. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a7.dist-info}/top_level.txt +0 -0
dtSpark/aws/pricing.py CHANGED
@@ -15,6 +15,13 @@ from datetime import datetime, timedelta
15
15
  from typing import Dict, Optional, Tuple
16
16
  from botocore.exceptions import ClientError
17
17
 
18
+ # Model ID constants
19
+ _MODEL_CLAUDE_35_SONNET_V2 = 'anthropic.claude-3-5-sonnet-20241022-v2:0'
20
+ _MODEL_CLAUDE_35_SONNET_V1 = 'anthropic.claude-3-5-sonnet-20240620-v1:0'
21
+ _MODEL_CLAUDE_3_OPUS = 'anthropic.claude-3-opus-20240229-v1:0'
22
+ _MODEL_CLAUDE_3_SONNET = 'anthropic.claude-3-sonnet-20240229-v1:0'
23
+ _MODEL_CLAUDE_3_HAIKU = 'anthropic.claude-3-haiku-20240307-v1:0'
24
+
18
25
 
19
26
  class BedrockPricing:
20
27
  """Manages AWS Bedrock pricing data and cost calculations."""
@@ -92,7 +99,7 @@ class BedrockPricing:
92
99
  # Fall back to hardcoded pricing
93
100
  logging.warning("All pricing fetch methods failed, using fallback pricing")
94
101
  self._use_fallback_pricing()
95
- return True
102
+ return False
96
103
 
97
104
  def _fetch_from_bulk_api(self) -> bool:
98
105
  """
@@ -163,7 +170,6 @@ class BedrockPricing:
163
170
  service_codes = ['AmazonBedrockFoundationModels', 'AmazonBedrockService', 'AmazonBedrock']
164
171
 
165
172
  all_price_lists = []
166
- successful_service_code = None
167
173
 
168
174
  for service_code in service_codes:
169
175
  try:
@@ -176,7 +182,6 @@ class BedrockPricing:
176
182
  price_list = response.get('PriceList', [])
177
183
  if price_list:
178
184
  all_price_lists.extend(price_list)
179
- successful_service_code = service_code
180
185
  logging.info(f"Found {len(price_list)} products with service code: {service_code}")
181
186
 
182
187
  # Continue fetching if there are more results
@@ -359,15 +364,15 @@ class BedrockPricing:
359
364
  elif 'sonnet 4' in model_lower or 'sonnet-4' in model_lower:
360
365
  return 'anthropic.claude-sonnet-4-0-v1:0'
361
366
  elif '3.5 sonnet v2' in model_lower or '3-5-sonnet-v2' in model_lower:
362
- return 'anthropic.claude-3-5-sonnet-20241022-v2:0'
367
+ return _MODEL_CLAUDE_35_SONNET_V2
363
368
  elif '3.5 sonnet' in model_lower or '3-5-sonnet' in model_lower:
364
- return 'anthropic.claude-3-5-sonnet-20240620-v1:0'
369
+ return _MODEL_CLAUDE_35_SONNET_V1
365
370
  elif '3 opus' in model_lower or '3-opus' in model_lower:
366
- return 'anthropic.claude-3-opus-20240229-v1:0'
371
+ return _MODEL_CLAUDE_3_OPUS
367
372
  elif '3 sonnet' in model_lower or '3-sonnet' in model_lower:
368
- return 'anthropic.claude-3-sonnet-20240229-v1:0'
373
+ return _MODEL_CLAUDE_3_SONNET
369
374
  elif '3 haiku' in model_lower or '3-haiku' in model_lower:
370
- return 'anthropic.claude-3-haiku-20240307-v1:0'
375
+ return _MODEL_CLAUDE_3_HAIKU
371
376
 
372
377
  # Amazon Titan models
373
378
  elif 'titan' in model_lower:
@@ -425,7 +430,6 @@ class BedrockPricing:
425
430
  for term_key, term_value in on_demand.items():
426
431
  price_dimensions = term_value.get('priceDimensions', {})
427
432
  for dim_key, dim_value in price_dimensions.items():
428
- unit = dim_value.get('unit', '')
429
433
  price_per_unit = float(dim_value.get('pricePerUnit', {}).get('USD', 0))
430
434
 
431
435
  # Determine if this is input or output pricing
@@ -484,26 +488,26 @@ class BedrockPricing:
484
488
  # Fallback pricing for common models (prices per 1000 tokens)
485
489
  fallback = {
486
490
  # Claude 3.5 Sonnet v2
487
- ('anthropic.claude-3-5-sonnet-20241022-v2:0', 'us-east-1'): {'input': 0.003, 'output': 0.015},
488
- ('anthropic.claude-3-5-sonnet-20241022-v2:0', 'us-west-2'): {'input': 0.003, 'output': 0.015},
489
- ('anthropic.claude-3-5-sonnet-20241022-v2:0', 'ap-southeast-2'): {'input': 0.003, 'output': 0.015},
491
+ (_MODEL_CLAUDE_35_SONNET_V2, 'us-east-1'): {'input': 0.003, 'output': 0.015},
492
+ (_MODEL_CLAUDE_35_SONNET_V2, 'us-west-2'): {'input': 0.003, 'output': 0.015},
493
+ (_MODEL_CLAUDE_35_SONNET_V2, 'ap-southeast-2'): {'input': 0.003, 'output': 0.015},
490
494
 
491
495
  # Claude 3.5 Sonnet v1
492
- ('anthropic.claude-3-5-sonnet-20240620-v1:0', 'us-east-1'): {'input': 0.003, 'output': 0.015},
493
- ('anthropic.claude-3-5-sonnet-20240620-v1:0', 'us-west-2'): {'input': 0.003, 'output': 0.015},
494
- ('anthropic.claude-3-5-sonnet-20240620-v1:0', 'ap-southeast-2'): {'input': 0.003, 'output': 0.015},
496
+ (_MODEL_CLAUDE_35_SONNET_V1, 'us-east-1'): {'input': 0.003, 'output': 0.015},
497
+ (_MODEL_CLAUDE_35_SONNET_V1, 'us-west-2'): {'input': 0.003, 'output': 0.015},
498
+ (_MODEL_CLAUDE_35_SONNET_V1, 'ap-southeast-2'): {'input': 0.003, 'output': 0.015},
495
499
 
496
500
  # Claude 3 Opus
497
- ('anthropic.claude-3-opus-20240229-v1:0', 'us-east-1'): {'input': 0.015, 'output': 0.075},
498
- ('anthropic.claude-3-opus-20240229-v1:0', 'us-west-2'): {'input': 0.015, 'output': 0.075},
501
+ (_MODEL_CLAUDE_3_OPUS, 'us-east-1'): {'input': 0.015, 'output': 0.075},
502
+ (_MODEL_CLAUDE_3_OPUS, 'us-west-2'): {'input': 0.015, 'output': 0.075},
499
503
 
500
504
  # Claude 3 Sonnet
501
- ('anthropic.claude-3-sonnet-20240229-v1:0', 'us-east-1'): {'input': 0.003, 'output': 0.015},
502
- ('anthropic.claude-3-sonnet-20240229-v1:0', 'us-west-2'): {'input': 0.003, 'output': 0.015},
505
+ (_MODEL_CLAUDE_3_SONNET, 'us-east-1'): {'input': 0.003, 'output': 0.015},
506
+ (_MODEL_CLAUDE_3_SONNET, 'us-west-2'): {'input': 0.003, 'output': 0.015},
503
507
 
504
508
  # Claude 3 Haiku
505
- ('anthropic.claude-3-haiku-20240307-v1:0', 'us-east-1'): {'input': 0.00025, 'output': 0.00125},
506
- ('anthropic.claude-3-haiku-20240307-v1:0', 'us-west-2'): {'input': 0.00025, 'output': 0.00125},
509
+ (_MODEL_CLAUDE_3_HAIKU, 'us-east-1'): {'input': 0.00025, 'output': 0.00125},
510
+ (_MODEL_CLAUDE_3_HAIKU, 'us-west-2'): {'input': 0.00025, 'output': 0.00125},
507
511
  }
508
512
 
509
513
  self.pricing_data = fallback
dtSpark/cli_interface.py CHANGED
@@ -27,6 +27,17 @@ import time
27
27
  import re
28
28
 
29
29
 
30
+ # Common style and message string constants (SonarCloud S1192)
31
+ _STYLE_BOLD_CYAN = "bold cyan"
32
+ _STYLE_BOLD_YELLOW = "bold yellow"
33
+ _STYLE_BOLD_MAGENTA = "bold magenta"
34
+ _MSG_INVALID_SELECTION = "Invalid selection"
35
+ _MSG_CANCEL_OPTION = " [0] Cancel"
36
+ _MSG_ENTER_CHOICE = "Enter choice"
37
+ _MSG_INVALID_CHOICE = "Invalid choice"
38
+ _MSG_INVALID_INPUT = "Invalid input"
39
+
40
+
30
41
  def extract_friendly_model_name(model_id: str) -> str:
31
42
  """
32
43
  Extract a human-friendly model name from a full model ID or ARN.
@@ -221,9 +232,11 @@ class CLIInterface:
221
232
  self.running = True
222
233
  self.model_changing_enabled = True # Can be disabled if model is locked via config
223
234
  self.cost_tracking_enabled = False # Can be enabled via config
235
+ self.actions_enabled = False # Can be enabled via autonomous_actions.enabled config
236
+ self.new_conversations_allowed = True # Can be disabled via predefined_conversations.allow_new_conversations
224
237
  self._active_status_indicator = None # Track active status indicator for pause/resume
225
238
 
226
- def print_splash_screen(self, full_name: str, description: str, version: str):
239
+ def print_splash_screen(self, full_name: str, description: str, version: str): # noqa: S1172
227
240
  """
228
241
  Print application splash screen with SPARK branding.
229
242
 
@@ -286,7 +299,7 @@ class CLIInterface:
286
299
  # This is now replaced by print_splash_screen
287
300
  pass
288
301
 
289
- def create_progress(self, description: str = "Initialising...") -> Progress:
302
+ def create_progress(self, description: str = "Initialising...") -> Progress: # noqa: S1172
290
303
  """
291
304
  Create a progress bar for tracking operations.
292
305
 
@@ -371,12 +384,13 @@ class CLIInterface:
371
384
  choice_map[str(option_num)] = 'costs'
372
385
  option_num += 1
373
386
 
374
- # Start New Conversation
375
- menu_content.append(" ", style="")
376
- menu_content.append(str(option_num), style="cyan")
377
- menu_content.append(". Start New Conversation\n", style="")
378
- choice_map[str(option_num)] = 'new'
379
- option_num += 1
387
+ # Start New Conversation (only when new conversations are allowed)
388
+ if self.new_conversations_allowed:
389
+ menu_content.append(" ", style="")
390
+ menu_content.append(str(option_num), style="cyan")
391
+ menu_content.append(". Start New Conversation\n", style="")
392
+ choice_map[str(option_num)] = 'new'
393
+ option_num += 1
380
394
 
381
395
  # List and Select Conversation
382
396
  menu_content.append(" ", style="")
@@ -385,12 +399,13 @@ class CLIInterface:
385
399
  choice_map[str(option_num)] = 'list'
386
400
  option_num += 1
387
401
 
388
- # Manage Autonomous Actions
389
- menu_content.append(" ", style="")
390
- menu_content.append(str(option_num), style="cyan")
391
- menu_content.append(". Manage Autonomous Actions\n", style="")
392
- choice_map[str(option_num)] = 'autonomous'
393
- option_num += 1
402
+ # Manage Autonomous Actions (only when enabled)
403
+ if self.actions_enabled:
404
+ menu_content.append(" ", style="")
405
+ menu_content.append(str(option_num), style="cyan")
406
+ menu_content.append(". Manage Autonomous Actions\n", style="")
407
+ choice_map[str(option_num)] = 'autonomous'
408
+ option_num += 1
394
409
 
395
410
  # Quit
396
411
  menu_content.append(" ", style="")
@@ -402,7 +417,7 @@ class CLIInterface:
402
417
  menu_panel = Panel(
403
418
  menu_content,
404
419
  title="[bold bright_magenta]MAIN MENU[/bold bright_magenta]",
405
- border_style="bold cyan",
420
+ border_style=_STYLE_BOLD_CYAN,
406
421
  box=box.HEAVY,
407
422
  padding=(0, 1)
408
423
  )
@@ -562,7 +577,7 @@ class CLIInterface:
562
577
  """
563
578
  # Create table
564
579
  table = Table(show_header=False, box=box.ROUNDED, border_style="cyan")
565
- table.add_column("No.", style="bold yellow", width=4)
580
+ table.add_column("No.", style=_STYLE_BOLD_YELLOW, width=4)
566
581
  table.add_column("Option", style="white")
567
582
 
568
583
  for i, option in enumerate(options, 1):
@@ -587,7 +602,7 @@ class CLIInterface:
587
602
  elif choice == len(options) + 1:
588
603
  return -1
589
604
  else:
590
- self.print_error("Invalid selection")
605
+ self.print_error(_MSG_INVALID_SELECTION)
591
606
  return -1
592
607
  except ValueError:
593
608
  self.print_error("Please enter a valid number")
@@ -612,7 +627,7 @@ class CLIInterface:
612
627
 
613
628
  self.console.print()
614
629
  self.print_separator("─")
615
- self.console.print(f"\n[bold yellow]🔐 Tool Permission Request[/bold yellow]")
630
+ self.console.print("\n[bold yellow]🔐 Tool Permission Request[/bold yellow]")
616
631
  self.console.print(f"\nThe assistant wants to use the tool: [bold cyan]{tool_name}[/bold cyan]")
617
632
 
618
633
  if tool_description:
@@ -665,11 +680,11 @@ class CLIInterface:
665
680
  # Create table
666
681
  table = Table(
667
682
  show_header=True,
668
- header_style="bold magenta",
683
+ header_style=_STYLE_BOLD_MAGENTA,
669
684
  box=box.ROUNDED,
670
685
  border_style="cyan"
671
686
  )
672
- table.add_column("No.", style="bold yellow", width=4)
687
+ table.add_column("No.", style=_STYLE_BOLD_YELLOW, width=4)
673
688
  table.add_column("Model Name", style="cyan")
674
689
  table.add_column("Provider", style="green")
675
690
  table.add_column("Access Method", style="magenta")
@@ -716,7 +731,7 @@ class CLIInterface:
716
731
  if 1 <= choice <= len(models):
717
732
  return models[choice - 1]['id']
718
733
  else:
719
- self.print_error("Invalid selection")
734
+ self.print_error(_MSG_INVALID_SELECTION)
720
735
  return None
721
736
  except ValueError:
722
737
  self.print_error("Please enter a valid number or Q to quit")
@@ -739,11 +754,11 @@ class CLIInterface:
739
754
  # Create table
740
755
  table = Table(
741
756
  show_header=True,
742
- header_style="bold magenta",
757
+ header_style=_STYLE_BOLD_MAGENTA,
743
758
  box=box.ROUNDED,
744
759
  border_style="cyan"
745
760
  )
746
- table.add_column("No.", style="bold yellow", width=4)
761
+ table.add_column("No.", style=_STYLE_BOLD_YELLOW, width=4)
747
762
  table.add_column("Name", style="cyan")
748
763
  table.add_column("Model", style="green")
749
764
  table.add_column("Created", style="blue")
@@ -789,7 +804,7 @@ class CLIInterface:
789
804
  if 1 <= choice <= len(conversations):
790
805
  return conversations[choice - 1]['id']
791
806
  else:
792
- self.print_error("Invalid selection")
807
+ self.print_error(_MSG_INVALID_SELECTION)
793
808
  return None
794
809
  except ValueError:
795
810
  self.print_error("Please enter a valid number or 'N' for new conversation")
@@ -847,7 +862,7 @@ class CLIInterface:
847
862
  self.console.print()
848
863
  self.console.print(Panel(
849
864
  "[bold]Conversation History[/bold]",
850
- style="bold magenta",
865
+ style=_STYLE_BOLD_MAGENTA,
851
866
  box=box.DOUBLE
852
867
  ))
853
868
 
@@ -879,7 +894,7 @@ class CLIInterface:
879
894
 
880
895
  # Create info table
881
896
  table = Table(show_header=False, box=None, padding=(0, 2))
882
- table.add_column("Label", style="bold cyan")
897
+ table.add_column("Label", style=_STYLE_BOLD_CYAN)
883
898
  table.add_column("Value", style="white")
884
899
 
885
900
  table.add_row("Conversation", conversation['name'])
@@ -900,12 +915,7 @@ class CLIInterface:
900
915
 
901
916
  # Add instructions indicator
902
917
  if conversation.get('instructions'):
903
- if detailed:
904
- # In detailed view, show YES and we'll display full instructions below
905
- table.add_row("Instructions", "[green]YES[/green]")
906
- else:
907
- # In regular view, just show YES
908
- table.add_row("Instructions", "[green]YES[/green]")
918
+ table.add_row("Instructions", "[green]YES[/green]")
909
919
  else:
910
920
  table.add_row("Instructions", "[dim]NO[/dim]")
911
921
 
@@ -1104,7 +1114,7 @@ class CLIInterface:
1104
1114
  try:
1105
1115
  from dtSpark import launch
1106
1116
  version = launch.version()
1107
- except:
1117
+ except ImportError:
1108
1118
  version = "X.X"
1109
1119
 
1110
1120
  # Get log path
@@ -1171,7 +1181,7 @@ class CLIInterface:
1171
1181
  # Create table for server details
1172
1182
  table = Table(
1173
1183
  show_header=True,
1174
- header_style="bold magenta",
1184
+ header_style=_STYLE_BOLD_MAGENTA,
1175
1185
  box=box.ROUNDED,
1176
1186
  border_style="green"
1177
1187
  )
@@ -1345,7 +1355,7 @@ class CLIInterface:
1345
1355
  # Budget section
1346
1356
  if budget_limit > 0:
1347
1357
  content_parts.append("")
1348
- content_parts.append(f"[bold cyan]Budget Status:[/bold cyan]")
1358
+ content_parts.append("[bold cyan]Budget Status:[/bold cyan]")
1349
1359
  content_parts.append(f" • Budget Limit: [yellow]${budget_limit:.2f} USD[/yellow]")
1350
1360
 
1351
1361
  if budget_exceeded:
@@ -1473,7 +1483,7 @@ class CLIInterface:
1473
1483
  f"[dim]({input_str})[/dim]"
1474
1484
  )
1475
1485
 
1476
- def display_tool_result(self, tool_name: str, result: str, is_error: bool = False):
1486
+ def display_tool_result(self, tool_name: str, result: str, is_error: bool = False): # noqa: S1172
1477
1487
  """
1478
1488
  Display a tool result during chat.
1479
1489
 
@@ -1555,7 +1565,7 @@ class CLIInterface:
1555
1565
  found_files = FileManager.scan_directory(str(path.absolute()), recursive=recursive)
1556
1566
 
1557
1567
  if not found_files:
1558
- self.print_warning(f" No supported files found in directory")
1568
+ self.print_warning(" No supported files found in directory")
1559
1569
  continue
1560
1570
 
1561
1571
  self.print_success(f" Found {len(found_files)} supported file(s)")
@@ -1605,7 +1615,6 @@ class CLIInterface:
1605
1615
 
1606
1616
  else:
1607
1617
  self.print_error(f"Invalid path type: {input_path}")
1608
- continue
1609
1618
 
1610
1619
  if file_attachments:
1611
1620
  self.console.print()
@@ -1636,7 +1645,7 @@ class CLIInterface:
1636
1645
  # Create table for attached files
1637
1646
  table = Table(
1638
1647
  show_header=True,
1639
- header_style="bold magenta",
1648
+ header_style=_STYLE_BOLD_MAGENTA,
1640
1649
  box=box.ROUNDED,
1641
1650
  border_style="blue"
1642
1651
  )
@@ -1700,7 +1709,7 @@ class CLIInterface:
1700
1709
  # Create table for transactions
1701
1710
  table = Table(
1702
1711
  show_header=True,
1703
- header_style="bold magenta",
1712
+ header_style=_STYLE_BOLD_MAGENTA,
1704
1713
  box=box.ROUNDED,
1705
1714
  border_style="yellow"
1706
1715
  )
@@ -1746,7 +1755,7 @@ class CLIInterface:
1746
1755
 
1747
1756
  # Create details table
1748
1757
  details_table = Table(show_header=False, box=None, padding=(0, 2))
1749
- details_table.add_column("Label", style="bold yellow")
1758
+ details_table.add_column("Label", style=_STYLE_BOLD_YELLOW)
1750
1759
  details_table.add_column("Value", style="white")
1751
1760
 
1752
1761
  timestamp = datetime.fromisoformat(transaction['transaction_timestamp'])
@@ -1781,7 +1790,7 @@ class CLIInterface:
1781
1790
  self.console.print()
1782
1791
  try:
1783
1792
  input_formatted = json.dumps(json.loads(transaction['tool_input']), indent=2)
1784
- except:
1793
+ except ValueError:
1785
1794
  input_formatted = transaction['tool_input']
1786
1795
 
1787
1796
  self.console.print(Panel(
@@ -1812,7 +1821,7 @@ class CLIInterface:
1812
1821
  """
1813
1822
  # Create stats table
1814
1823
  stats_table = Table(show_header=False, box=None, padding=(0, 2))
1815
- stats_table.add_column("Metric", style="bold yellow")
1824
+ stats_table.add_column("Metric", style=_STYLE_BOLD_YELLOW)
1816
1825
  stats_table.add_column("Value", style="white")
1817
1826
 
1818
1827
  stats_table.add_row("Total Transactions", f"{stats['total_transactions']:,}")
@@ -1831,7 +1840,7 @@ class CLIInterface:
1831
1840
  self.console.print()
1832
1841
  tools_table = Table(
1833
1842
  show_header=True,
1834
- header_style="bold magenta",
1843
+ header_style=_STYLE_BOLD_MAGENTA,
1835
1844
  box=box.ROUNDED,
1836
1845
  border_style="green"
1837
1846
  )
@@ -1852,7 +1861,7 @@ class CLIInterface:
1852
1861
  self.console.print()
1853
1862
  conv_table = Table(
1854
1863
  show_header=True,
1855
- header_style="bold magenta",
1864
+ header_style=_STYLE_BOLD_MAGENTA,
1856
1865
  box=box.ROUNDED,
1857
1866
  border_style="cyan"
1858
1867
  )
@@ -1885,7 +1894,7 @@ class CLIInterface:
1885
1894
 
1886
1895
  table = Table(
1887
1896
  show_header=True,
1888
- header_style="bold magenta",
1897
+ header_style=_STYLE_BOLD_MAGENTA,
1889
1898
  box=box.ROUNDED,
1890
1899
  border_style="cyan"
1891
1900
  )
@@ -2046,9 +2055,9 @@ class CLIInterface:
2046
2055
  for i, state in enumerate(server_states, 1):
2047
2056
  status = "[green]enabled[/green]" if state['enabled'] else "[red]disabled[/red]"
2048
2057
  self.console.print(f" [{i}] {state['server_name']} ({status})")
2049
- self.console.print(" [0] Cancel")
2058
+ self.console.print(_MSG_CANCEL_OPTION)
2050
2059
 
2051
- choice = self.get_input("Enter choice")
2060
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2052
2061
  try:
2053
2062
  idx = int(choice)
2054
2063
  if idx == 0:
@@ -2056,10 +2065,10 @@ class CLIInterface:
2056
2065
  if 1 <= idx <= len(server_states):
2057
2066
  return server_states[idx - 1]['server_name']
2058
2067
  else:
2059
- self.print_error("Invalid choice")
2068
+ self.print_error(_MSG_INVALID_CHOICE)
2060
2069
  return None
2061
2070
  except ValueError:
2062
- self.print_error("Invalid input")
2071
+ self.print_error(_MSG_INVALID_INPUT)
2063
2072
  return None
2064
2073
 
2065
2074
  # =========================================================================
@@ -2103,7 +2112,7 @@ class CLIInterface:
2103
2112
  menu_panel = Panel(
2104
2113
  menu_content,
2105
2114
  title="[bold bright_magenta]AUTONOMOUS ACTIONS[/bold bright_magenta]",
2106
- border_style="bold cyan",
2115
+ border_style=_STYLE_BOLD_CYAN,
2107
2116
  box=box.HEAVY,
2108
2117
  padding=(0, 1)
2109
2118
  )
@@ -2149,7 +2158,7 @@ class CLIInterface:
2149
2158
 
2150
2159
  table = Table(
2151
2160
  show_header=True,
2152
- header_style="bold magenta",
2161
+ header_style=_STYLE_BOLD_MAGENTA,
2153
2162
  box=box.ROUNDED,
2154
2163
  border_style="cyan"
2155
2164
  )
@@ -2229,7 +2238,7 @@ class CLIInterface:
2229
2238
 
2230
2239
  table = Table(
2231
2240
  show_header=True,
2232
- header_style="bold magenta",
2241
+ header_style=_STYLE_BOLD_MAGENTA,
2233
2242
  box=box.ROUNDED,
2234
2243
  border_style="cyan"
2235
2244
  )
@@ -2264,7 +2273,7 @@ class CLIInterface:
2264
2273
  completed = datetime.fromisoformat(completed.replace('Z', '+00:00'))
2265
2274
  duration = (completed - started).total_seconds()
2266
2275
  duration_str = f"{duration:.1f}s"
2267
- except:
2276
+ except (ValueError, TypeError):
2268
2277
  duration_str = "N/A"
2269
2278
  else:
2270
2279
  duration_str = "N/A"
@@ -2347,9 +2356,9 @@ class CLIInterface:
2347
2356
  for i, action in enumerate(actions, 1):
2348
2357
  status = "[green]enabled[/green]" if action['is_enabled'] else "[red]disabled[/red]"
2349
2358
  self.console.print(f" [{i}] {action['name']} ({status})")
2350
- self.console.print(" [0] Cancel")
2359
+ self.console.print(_MSG_CANCEL_OPTION)
2351
2360
 
2352
- choice = self.get_input("Enter choice")
2361
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2353
2362
  try:
2354
2363
  idx = int(choice)
2355
2364
  if idx == 0:
@@ -2357,10 +2366,10 @@ class CLIInterface:
2357
2366
  if 1 <= idx <= len(actions):
2358
2367
  return actions[idx - 1]['id']
2359
2368
  else:
2360
- self.print_error("Invalid choice")
2369
+ self.print_error(_MSG_INVALID_CHOICE)
2361
2370
  return None
2362
2371
  except ValueError:
2363
- self.print_error("Invalid input")
2372
+ self.print_error(_MSG_INVALID_INPUT)
2364
2373
  return None
2365
2374
 
2366
2375
  def select_run(self, runs: List[Dict], prompt: str = "Select a run") -> Optional[int]:
@@ -2383,9 +2392,9 @@ class CLIInterface:
2383
2392
  status = run.get('status', 'unknown')
2384
2393
  started = str(run.get('started_at', ''))[:16]
2385
2394
  self.console.print(f" [{i}] Run {run['id']} - {status} ({started})")
2386
- self.console.print(" [0] Cancel")
2395
+ self.console.print(_MSG_CANCEL_OPTION)
2387
2396
 
2388
- choice = self.get_input("Enter choice")
2397
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2389
2398
  try:
2390
2399
  idx = int(choice)
2391
2400
  if idx == 0:
@@ -2393,10 +2402,10 @@ class CLIInterface:
2393
2402
  if 1 <= idx <= len(runs):
2394
2403
  return runs[idx - 1]['id']
2395
2404
  else:
2396
- self.print_error("Invalid choice")
2405
+ self.print_error(_MSG_INVALID_CHOICE)
2397
2406
  return None
2398
2407
  except ValueError:
2399
- self.print_error("Invalid input")
2408
+ self.print_error(_MSG_INVALID_INPUT)
2400
2409
  return None
2401
2410
 
2402
2411
  def select_export_format(self) -> Optional[str]:
@@ -2410,9 +2419,9 @@ class CLIInterface:
2410
2419
  self.console.print(" [1] Plain Text")
2411
2420
  self.console.print(" [2] HTML")
2412
2421
  self.console.print(" [3] Markdown")
2413
- self.console.print(" [0] Cancel")
2422
+ self.console.print(_MSG_CANCEL_OPTION)
2414
2423
 
2415
- choice = self.get_input("Enter choice")
2424
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2416
2425
  format_map = {'1': 'text', '2': 'html', '3': 'markdown'}
2417
2426
  return format_map.get(choice)
2418
2427
 
@@ -2459,7 +2468,7 @@ class CLIInterface:
2459
2468
  friendly_name = extract_friendly_model_name(model.get('id', ''))
2460
2469
  self.console.print(f" [{i}] {friendly_name}")
2461
2470
 
2462
- model_choice = self.get_input("Enter choice")
2471
+ model_choice = self.get_input(_MSG_ENTER_CHOICE)
2463
2472
  try:
2464
2473
  model_idx = int(model_choice) - 1
2465
2474
  if model_idx < 0 or model_idx >= len(available_models):
@@ -2467,7 +2476,7 @@ class CLIInterface:
2467
2476
  return None
2468
2477
  model_id = available_models[model_idx]['id']
2469
2478
  except ValueError:
2470
- self.print_error("Invalid input")
2479
+ self.print_error(_MSG_INVALID_INPUT)
2471
2480
  return None
2472
2481
 
2473
2482
  # Step 5: Schedule type
@@ -2475,7 +2484,7 @@ class CLIInterface:
2475
2484
  self.console.print(" [1] One-off (run once at specific time)")
2476
2485
  self.console.print(" [2] Recurring (run on schedule)")
2477
2486
 
2478
- schedule_choice = self.get_input("Enter choice")
2487
+ schedule_choice = self.get_input(_MSG_ENTER_CHOICE)
2479
2488
  if schedule_choice == '1':
2480
2489
  schedule_type = 'one_off'
2481
2490
  self.console.print("\n[dim]Enter date/time in format: YYYY-MM-DD HH:MM[/dim]")
@@ -2504,7 +2513,7 @@ class CLIInterface:
2504
2513
  self.console.print(" [1] Fresh - Start with clean context each run")
2505
2514
  self.console.print(" [2] Cumulative - Carry context from previous runs")
2506
2515
 
2507
- context_choice = self.get_input("Enter choice")
2516
+ context_choice = self.get_input(_MSG_ENTER_CHOICE)
2508
2517
  context_mode = 'cumulative' if context_choice == '2' else 'fresh'
2509
2518
 
2510
2519
  # Step 7: Max failures