dtSpark 1.1.0a3__py3-none-any.whl → 1.1.0a6__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 +69 -62
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +112 -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 +10 -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 +11 -9
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +65 -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 +13 -0
  43. dtSpark/web/templates/chat.html +10 -10
  44. dtSpark/web/templates/conversations.html +2 -2
  45. dtSpark/web/templates/goodbye.html +2 -2
  46. dtSpark/web/templates/main_menu.html +17 -17
  47. dtSpark/web/web_interface.py +2 -2
  48. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/METADATA +9 -2
  49. dtspark-1.1.0a6.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.0a6.dist-info}/WHEEL +0 -0
  52. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/entry_points.txt +0 -0
  53. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.dist-info}/licenses/LICENSE +0 -0
  54. {dtspark-1.1.0a3.dist-info → dtspark-1.1.0a6.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,10 @@ 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
224
236
  self._active_status_indicator = None # Track active status indicator for pause/resume
225
237
 
226
- def print_splash_screen(self, full_name: str, description: str, version: str):
238
+ def print_splash_screen(self, full_name: str, description: str, version: str): # noqa: S1172
227
239
  """
228
240
  Print application splash screen with SPARK branding.
229
241
 
@@ -286,7 +298,7 @@ class CLIInterface:
286
298
  # This is now replaced by print_splash_screen
287
299
  pass
288
300
 
289
- def create_progress(self, description: str = "Initialising...") -> Progress:
301
+ def create_progress(self, description: str = "Initialising...") -> Progress: # noqa: S1172
290
302
  """
291
303
  Create a progress bar for tracking operations.
292
304
 
@@ -385,12 +397,13 @@ class CLIInterface:
385
397
  choice_map[str(option_num)] = 'list'
386
398
  option_num += 1
387
399
 
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
400
+ # Manage Autonomous Actions (only when enabled)
401
+ if self.actions_enabled:
402
+ menu_content.append(" ", style="")
403
+ menu_content.append(str(option_num), style="cyan")
404
+ menu_content.append(". Manage Autonomous Actions\n", style="")
405
+ choice_map[str(option_num)] = 'autonomous'
406
+ option_num += 1
394
407
 
395
408
  # Quit
396
409
  menu_content.append(" ", style="")
@@ -402,7 +415,7 @@ class CLIInterface:
402
415
  menu_panel = Panel(
403
416
  menu_content,
404
417
  title="[bold bright_magenta]MAIN MENU[/bold bright_magenta]",
405
- border_style="bold cyan",
418
+ border_style=_STYLE_BOLD_CYAN,
406
419
  box=box.HEAVY,
407
420
  padding=(0, 1)
408
421
  )
@@ -562,7 +575,7 @@ class CLIInterface:
562
575
  """
563
576
  # Create table
564
577
  table = Table(show_header=False, box=box.ROUNDED, border_style="cyan")
565
- table.add_column("No.", style="bold yellow", width=4)
578
+ table.add_column("No.", style=_STYLE_BOLD_YELLOW, width=4)
566
579
  table.add_column("Option", style="white")
567
580
 
568
581
  for i, option in enumerate(options, 1):
@@ -587,7 +600,7 @@ class CLIInterface:
587
600
  elif choice == len(options) + 1:
588
601
  return -1
589
602
  else:
590
- self.print_error("Invalid selection")
603
+ self.print_error(_MSG_INVALID_SELECTION)
591
604
  return -1
592
605
  except ValueError:
593
606
  self.print_error("Please enter a valid number")
@@ -612,7 +625,7 @@ class CLIInterface:
612
625
 
613
626
  self.console.print()
614
627
  self.print_separator("─")
615
- self.console.print(f"\n[bold yellow]🔐 Tool Permission Request[/bold yellow]")
628
+ self.console.print("\n[bold yellow]🔐 Tool Permission Request[/bold yellow]")
616
629
  self.console.print(f"\nThe assistant wants to use the tool: [bold cyan]{tool_name}[/bold cyan]")
617
630
 
618
631
  if tool_description:
@@ -665,11 +678,11 @@ class CLIInterface:
665
678
  # Create table
666
679
  table = Table(
667
680
  show_header=True,
668
- header_style="bold magenta",
681
+ header_style=_STYLE_BOLD_MAGENTA,
669
682
  box=box.ROUNDED,
670
683
  border_style="cyan"
671
684
  )
672
- table.add_column("No.", style="bold yellow", width=4)
685
+ table.add_column("No.", style=_STYLE_BOLD_YELLOW, width=4)
673
686
  table.add_column("Model Name", style="cyan")
674
687
  table.add_column("Provider", style="green")
675
688
  table.add_column("Access Method", style="magenta")
@@ -716,7 +729,7 @@ class CLIInterface:
716
729
  if 1 <= choice <= len(models):
717
730
  return models[choice - 1]['id']
718
731
  else:
719
- self.print_error("Invalid selection")
732
+ self.print_error(_MSG_INVALID_SELECTION)
720
733
  return None
721
734
  except ValueError:
722
735
  self.print_error("Please enter a valid number or Q to quit")
@@ -739,11 +752,11 @@ class CLIInterface:
739
752
  # Create table
740
753
  table = Table(
741
754
  show_header=True,
742
- header_style="bold magenta",
755
+ header_style=_STYLE_BOLD_MAGENTA,
743
756
  box=box.ROUNDED,
744
757
  border_style="cyan"
745
758
  )
746
- table.add_column("No.", style="bold yellow", width=4)
759
+ table.add_column("No.", style=_STYLE_BOLD_YELLOW, width=4)
747
760
  table.add_column("Name", style="cyan")
748
761
  table.add_column("Model", style="green")
749
762
  table.add_column("Created", style="blue")
@@ -789,7 +802,7 @@ class CLIInterface:
789
802
  if 1 <= choice <= len(conversations):
790
803
  return conversations[choice - 1]['id']
791
804
  else:
792
- self.print_error("Invalid selection")
805
+ self.print_error(_MSG_INVALID_SELECTION)
793
806
  return None
794
807
  except ValueError:
795
808
  self.print_error("Please enter a valid number or 'N' for new conversation")
@@ -847,7 +860,7 @@ class CLIInterface:
847
860
  self.console.print()
848
861
  self.console.print(Panel(
849
862
  "[bold]Conversation History[/bold]",
850
- style="bold magenta",
863
+ style=_STYLE_BOLD_MAGENTA,
851
864
  box=box.DOUBLE
852
865
  ))
853
866
 
@@ -879,7 +892,7 @@ class CLIInterface:
879
892
 
880
893
  # Create info table
881
894
  table = Table(show_header=False, box=None, padding=(0, 2))
882
- table.add_column("Label", style="bold cyan")
895
+ table.add_column("Label", style=_STYLE_BOLD_CYAN)
883
896
  table.add_column("Value", style="white")
884
897
 
885
898
  table.add_row("Conversation", conversation['name'])
@@ -900,12 +913,7 @@ class CLIInterface:
900
913
 
901
914
  # Add instructions indicator
902
915
  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]")
916
+ table.add_row("Instructions", "[green]YES[/green]")
909
917
  else:
910
918
  table.add_row("Instructions", "[dim]NO[/dim]")
911
919
 
@@ -1104,7 +1112,7 @@ class CLIInterface:
1104
1112
  try:
1105
1113
  from dtSpark import launch
1106
1114
  version = launch.version()
1107
- except:
1115
+ except ImportError:
1108
1116
  version = "X.X"
1109
1117
 
1110
1118
  # Get log path
@@ -1171,7 +1179,7 @@ class CLIInterface:
1171
1179
  # Create table for server details
1172
1180
  table = Table(
1173
1181
  show_header=True,
1174
- header_style="bold magenta",
1182
+ header_style=_STYLE_BOLD_MAGENTA,
1175
1183
  box=box.ROUNDED,
1176
1184
  border_style="green"
1177
1185
  )
@@ -1345,7 +1353,7 @@ class CLIInterface:
1345
1353
  # Budget section
1346
1354
  if budget_limit > 0:
1347
1355
  content_parts.append("")
1348
- content_parts.append(f"[bold cyan]Budget Status:[/bold cyan]")
1356
+ content_parts.append("[bold cyan]Budget Status:[/bold cyan]")
1349
1357
  content_parts.append(f" • Budget Limit: [yellow]${budget_limit:.2f} USD[/yellow]")
1350
1358
 
1351
1359
  if budget_exceeded:
@@ -1473,7 +1481,7 @@ class CLIInterface:
1473
1481
  f"[dim]({input_str})[/dim]"
1474
1482
  )
1475
1483
 
1476
- def display_tool_result(self, tool_name: str, result: str, is_error: bool = False):
1484
+ def display_tool_result(self, tool_name: str, result: str, is_error: bool = False): # noqa: S1172
1477
1485
  """
1478
1486
  Display a tool result during chat.
1479
1487
 
@@ -1555,7 +1563,7 @@ class CLIInterface:
1555
1563
  found_files = FileManager.scan_directory(str(path.absolute()), recursive=recursive)
1556
1564
 
1557
1565
  if not found_files:
1558
- self.print_warning(f" No supported files found in directory")
1566
+ self.print_warning(" No supported files found in directory")
1559
1567
  continue
1560
1568
 
1561
1569
  self.print_success(f" Found {len(found_files)} supported file(s)")
@@ -1605,7 +1613,6 @@ class CLIInterface:
1605
1613
 
1606
1614
  else:
1607
1615
  self.print_error(f"Invalid path type: {input_path}")
1608
- continue
1609
1616
 
1610
1617
  if file_attachments:
1611
1618
  self.console.print()
@@ -1636,7 +1643,7 @@ class CLIInterface:
1636
1643
  # Create table for attached files
1637
1644
  table = Table(
1638
1645
  show_header=True,
1639
- header_style="bold magenta",
1646
+ header_style=_STYLE_BOLD_MAGENTA,
1640
1647
  box=box.ROUNDED,
1641
1648
  border_style="blue"
1642
1649
  )
@@ -1700,7 +1707,7 @@ class CLIInterface:
1700
1707
  # Create table for transactions
1701
1708
  table = Table(
1702
1709
  show_header=True,
1703
- header_style="bold magenta",
1710
+ header_style=_STYLE_BOLD_MAGENTA,
1704
1711
  box=box.ROUNDED,
1705
1712
  border_style="yellow"
1706
1713
  )
@@ -1746,7 +1753,7 @@ class CLIInterface:
1746
1753
 
1747
1754
  # Create details table
1748
1755
  details_table = Table(show_header=False, box=None, padding=(0, 2))
1749
- details_table.add_column("Label", style="bold yellow")
1756
+ details_table.add_column("Label", style=_STYLE_BOLD_YELLOW)
1750
1757
  details_table.add_column("Value", style="white")
1751
1758
 
1752
1759
  timestamp = datetime.fromisoformat(transaction['transaction_timestamp'])
@@ -1781,7 +1788,7 @@ class CLIInterface:
1781
1788
  self.console.print()
1782
1789
  try:
1783
1790
  input_formatted = json.dumps(json.loads(transaction['tool_input']), indent=2)
1784
- except:
1791
+ except ValueError:
1785
1792
  input_formatted = transaction['tool_input']
1786
1793
 
1787
1794
  self.console.print(Panel(
@@ -1812,7 +1819,7 @@ class CLIInterface:
1812
1819
  """
1813
1820
  # Create stats table
1814
1821
  stats_table = Table(show_header=False, box=None, padding=(0, 2))
1815
- stats_table.add_column("Metric", style="bold yellow")
1822
+ stats_table.add_column("Metric", style=_STYLE_BOLD_YELLOW)
1816
1823
  stats_table.add_column("Value", style="white")
1817
1824
 
1818
1825
  stats_table.add_row("Total Transactions", f"{stats['total_transactions']:,}")
@@ -1831,7 +1838,7 @@ class CLIInterface:
1831
1838
  self.console.print()
1832
1839
  tools_table = Table(
1833
1840
  show_header=True,
1834
- header_style="bold magenta",
1841
+ header_style=_STYLE_BOLD_MAGENTA,
1835
1842
  box=box.ROUNDED,
1836
1843
  border_style="green"
1837
1844
  )
@@ -1852,7 +1859,7 @@ class CLIInterface:
1852
1859
  self.console.print()
1853
1860
  conv_table = Table(
1854
1861
  show_header=True,
1855
- header_style="bold magenta",
1862
+ header_style=_STYLE_BOLD_MAGENTA,
1856
1863
  box=box.ROUNDED,
1857
1864
  border_style="cyan"
1858
1865
  )
@@ -1885,7 +1892,7 @@ class CLIInterface:
1885
1892
 
1886
1893
  table = Table(
1887
1894
  show_header=True,
1888
- header_style="bold magenta",
1895
+ header_style=_STYLE_BOLD_MAGENTA,
1889
1896
  box=box.ROUNDED,
1890
1897
  border_style="cyan"
1891
1898
  )
@@ -2046,9 +2053,9 @@ class CLIInterface:
2046
2053
  for i, state in enumerate(server_states, 1):
2047
2054
  status = "[green]enabled[/green]" if state['enabled'] else "[red]disabled[/red]"
2048
2055
  self.console.print(f" [{i}] {state['server_name']} ({status})")
2049
- self.console.print(" [0] Cancel")
2056
+ self.console.print(_MSG_CANCEL_OPTION)
2050
2057
 
2051
- choice = self.get_input("Enter choice")
2058
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2052
2059
  try:
2053
2060
  idx = int(choice)
2054
2061
  if idx == 0:
@@ -2056,10 +2063,10 @@ class CLIInterface:
2056
2063
  if 1 <= idx <= len(server_states):
2057
2064
  return server_states[idx - 1]['server_name']
2058
2065
  else:
2059
- self.print_error("Invalid choice")
2066
+ self.print_error(_MSG_INVALID_CHOICE)
2060
2067
  return None
2061
2068
  except ValueError:
2062
- self.print_error("Invalid input")
2069
+ self.print_error(_MSG_INVALID_INPUT)
2063
2070
  return None
2064
2071
 
2065
2072
  # =========================================================================
@@ -2103,7 +2110,7 @@ class CLIInterface:
2103
2110
  menu_panel = Panel(
2104
2111
  menu_content,
2105
2112
  title="[bold bright_magenta]AUTONOMOUS ACTIONS[/bold bright_magenta]",
2106
- border_style="bold cyan",
2113
+ border_style=_STYLE_BOLD_CYAN,
2107
2114
  box=box.HEAVY,
2108
2115
  padding=(0, 1)
2109
2116
  )
@@ -2149,7 +2156,7 @@ class CLIInterface:
2149
2156
 
2150
2157
  table = Table(
2151
2158
  show_header=True,
2152
- header_style="bold magenta",
2159
+ header_style=_STYLE_BOLD_MAGENTA,
2153
2160
  box=box.ROUNDED,
2154
2161
  border_style="cyan"
2155
2162
  )
@@ -2229,7 +2236,7 @@ class CLIInterface:
2229
2236
 
2230
2237
  table = Table(
2231
2238
  show_header=True,
2232
- header_style="bold magenta",
2239
+ header_style=_STYLE_BOLD_MAGENTA,
2233
2240
  box=box.ROUNDED,
2234
2241
  border_style="cyan"
2235
2242
  )
@@ -2264,7 +2271,7 @@ class CLIInterface:
2264
2271
  completed = datetime.fromisoformat(completed.replace('Z', '+00:00'))
2265
2272
  duration = (completed - started).total_seconds()
2266
2273
  duration_str = f"{duration:.1f}s"
2267
- except:
2274
+ except (ValueError, TypeError):
2268
2275
  duration_str = "N/A"
2269
2276
  else:
2270
2277
  duration_str = "N/A"
@@ -2347,9 +2354,9 @@ class CLIInterface:
2347
2354
  for i, action in enumerate(actions, 1):
2348
2355
  status = "[green]enabled[/green]" if action['is_enabled'] else "[red]disabled[/red]"
2349
2356
  self.console.print(f" [{i}] {action['name']} ({status})")
2350
- self.console.print(" [0] Cancel")
2357
+ self.console.print(_MSG_CANCEL_OPTION)
2351
2358
 
2352
- choice = self.get_input("Enter choice")
2359
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2353
2360
  try:
2354
2361
  idx = int(choice)
2355
2362
  if idx == 0:
@@ -2357,10 +2364,10 @@ class CLIInterface:
2357
2364
  if 1 <= idx <= len(actions):
2358
2365
  return actions[idx - 1]['id']
2359
2366
  else:
2360
- self.print_error("Invalid choice")
2367
+ self.print_error(_MSG_INVALID_CHOICE)
2361
2368
  return None
2362
2369
  except ValueError:
2363
- self.print_error("Invalid input")
2370
+ self.print_error(_MSG_INVALID_INPUT)
2364
2371
  return None
2365
2372
 
2366
2373
  def select_run(self, runs: List[Dict], prompt: str = "Select a run") -> Optional[int]:
@@ -2383,9 +2390,9 @@ class CLIInterface:
2383
2390
  status = run.get('status', 'unknown')
2384
2391
  started = str(run.get('started_at', ''))[:16]
2385
2392
  self.console.print(f" [{i}] Run {run['id']} - {status} ({started})")
2386
- self.console.print(" [0] Cancel")
2393
+ self.console.print(_MSG_CANCEL_OPTION)
2387
2394
 
2388
- choice = self.get_input("Enter choice")
2395
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2389
2396
  try:
2390
2397
  idx = int(choice)
2391
2398
  if idx == 0:
@@ -2393,10 +2400,10 @@ class CLIInterface:
2393
2400
  if 1 <= idx <= len(runs):
2394
2401
  return runs[idx - 1]['id']
2395
2402
  else:
2396
- self.print_error("Invalid choice")
2403
+ self.print_error(_MSG_INVALID_CHOICE)
2397
2404
  return None
2398
2405
  except ValueError:
2399
- self.print_error("Invalid input")
2406
+ self.print_error(_MSG_INVALID_INPUT)
2400
2407
  return None
2401
2408
 
2402
2409
  def select_export_format(self) -> Optional[str]:
@@ -2410,9 +2417,9 @@ class CLIInterface:
2410
2417
  self.console.print(" [1] Plain Text")
2411
2418
  self.console.print(" [2] HTML")
2412
2419
  self.console.print(" [3] Markdown")
2413
- self.console.print(" [0] Cancel")
2420
+ self.console.print(_MSG_CANCEL_OPTION)
2414
2421
 
2415
- choice = self.get_input("Enter choice")
2422
+ choice = self.get_input(_MSG_ENTER_CHOICE)
2416
2423
  format_map = {'1': 'text', '2': 'html', '3': 'markdown'}
2417
2424
  return format_map.get(choice)
2418
2425
 
@@ -2459,7 +2466,7 @@ class CLIInterface:
2459
2466
  friendly_name = extract_friendly_model_name(model.get('id', ''))
2460
2467
  self.console.print(f" [{i}] {friendly_name}")
2461
2468
 
2462
- model_choice = self.get_input("Enter choice")
2469
+ model_choice = self.get_input(_MSG_ENTER_CHOICE)
2463
2470
  try:
2464
2471
  model_idx = int(model_choice) - 1
2465
2472
  if model_idx < 0 or model_idx >= len(available_models):
@@ -2467,7 +2474,7 @@ class CLIInterface:
2467
2474
  return None
2468
2475
  model_id = available_models[model_idx]['id']
2469
2476
  except ValueError:
2470
- self.print_error("Invalid input")
2477
+ self.print_error(_MSG_INVALID_INPUT)
2471
2478
  return None
2472
2479
 
2473
2480
  # Step 5: Schedule type
@@ -2475,7 +2482,7 @@ class CLIInterface:
2475
2482
  self.console.print(" [1] One-off (run once at specific time)")
2476
2483
  self.console.print(" [2] Recurring (run on schedule)")
2477
2484
 
2478
- schedule_choice = self.get_input("Enter choice")
2485
+ schedule_choice = self.get_input(_MSG_ENTER_CHOICE)
2479
2486
  if schedule_choice == '1':
2480
2487
  schedule_type = 'one_off'
2481
2488
  self.console.print("\n[dim]Enter date/time in format: YYYY-MM-DD HH:MM[/dim]")
@@ -2504,7 +2511,7 @@ class CLIInterface:
2504
2511
  self.console.print(" [1] Fresh - Start with clean context each run")
2505
2512
  self.console.print(" [2] Cumulative - Carry context from previous runs")
2506
2513
 
2507
- context_choice = self.get_input("Enter choice")
2514
+ context_choice = self.get_input(_MSG_ENTER_CHOICE)
2508
2515
  context_mode = 'cumulative' if context_choice == '2' else 'fresh'
2509
2516
 
2510
2517
  # Step 7: Max failures