janito 0.14.0__py3-none-any.whl → 0.15.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,31 +1,31 @@
1
- """
2
- Workspace management functions for Janito CLI.
3
- """
4
- import sys
5
- from typing import Optional
6
- from rich.console import Console
7
-
8
- from janito.config import get_config
9
-
10
- console = Console()
11
-
12
- def handle_workspace(workspace: Optional[str]) -> bool:
13
- """
14
- Handle the --workspace parameter.
15
-
16
- Args:
17
- workspace: Workspace directory path
18
-
19
- Returns:
20
- bool: True if the program should exit after this operation
21
- """
22
- if workspace:
23
- try:
24
- console.print(f"[bold]📂 Setting workspace directory to: {workspace}[/bold]")
25
- get_config().workspace_dir = workspace
26
- console.print(f"[bold green]✅ Workspace directory set to: {get_config().workspace_dir}[/bold green]")
27
- except ValueError as e:
28
- console.print(f"[bold red]Error:[/bold red] {str(e)}")
29
- sys.exit(1)
30
-
1
+ """
2
+ Workspace management functions for Janito CLI.
3
+ """
4
+ import sys
5
+ from typing import Optional
6
+ from rich.console import Console
7
+
8
+ from janito.config import Config
9
+
10
+ console = Console()
11
+
12
+ def handle_workspace(workspace: Optional[str]) -> bool:
13
+ """
14
+ Handle the --workspace parameter.
15
+
16
+ Args:
17
+ workspace: Workspace directory path
18
+
19
+ Returns:
20
+ bool: True if the program should exit after this operation
21
+ """
22
+ if workspace:
23
+ try:
24
+ console.print(f"[bold]📂 Setting workspace directory to: {workspace}[/bold]")
25
+ Config().workspace_dir = workspace
26
+ console.print(f"[bold green]✅ Workspace directory set to: {Config().workspace_dir}[/bold green]")
27
+ except ValueError as e:
28
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
29
+ sys.exit(1)
30
+
31
31
  return False
@@ -0,0 +1,104 @@
1
+ # Janito Configuration System
2
+
3
+ This directory contains the configuration system for Janito. The configuration system is designed to be modular, extensible, and easy to use.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ janito/config/
9
+ ├── __init__.py # Re-exports and backward compatibility
10
+ ├── README.md # This file
11
+ ├── core/ # Core configuration functionality
12
+ │ ├── __init__.py # Re-exports core components
13
+ │ ├── singleton.py # Singleton implementation
14
+ │ ├── properties.py # Property getters and setters
15
+ │ └── file_operations.py # File I/O operations
16
+ ├── profiles/ # Profile management
17
+ │ ├── __init__.py # Re-exports profile components
18
+ │ ├── definitions.py # Profile definitions
19
+ │ └── manager.py # Profile management functions
20
+ └── cli/ # CLI integration
21
+ ├── __init__.py # Re-exports CLI components
22
+ ├── commands.py # Command handling functions
23
+ └── validators.py # Input validation functions
24
+ ```
25
+
26
+ ## Core Components
27
+
28
+ The core configuration functionality is implemented in the `core` directory:
29
+
30
+ - `singleton.py`: Implements the `Config` class as a singleton to ensure only one instance exists
31
+ - `properties.py`: Contains property getters and setters for the `Config` class
32
+ - `file_operations.py`: Handles file I/O operations for loading and saving configuration files
33
+
34
+ ## Profiles
35
+
36
+ The `profiles` directory contains functionality related to parameter profiles:
37
+
38
+ - `definitions.py`: Defines predefined parameter profiles (precise, balanced, conversational, creative, technical)
39
+ - `manager.py`: Provides functions for managing profiles, including getting available profiles and creating custom profiles
40
+
41
+ ## CLI Integration
42
+
43
+ The `cli` directory contains functionality related to CLI integration:
44
+
45
+ - `commands.py`: Implements command handling functions for configuration-related CLI commands
46
+ - `validators.py`: Provides validation functions for configuration inputs
47
+
48
+ ## Usage
49
+
50
+ ### Basic Usage
51
+
52
+ ```python
53
+ from janito.config import Config
54
+
55
+ # Get the singleton instance
56
+ config = Config()
57
+
58
+ # Access configuration properties
59
+ workspace_dir = config.workspace_dir
60
+ temperature = config.temperature
61
+ role = config.role
62
+
63
+ # Set configuration properties
64
+ config.temperature = 0.7 # Set runtime value only
65
+ config.temperature = (0.7, "local") # Set in local config
66
+ config.temperature = (0.7, "global") # Set in global config
67
+ ```
68
+
69
+ ### Working with Profiles
70
+
71
+ ```python
72
+ from janito.config import Config, get_available_profiles, get_profile
73
+
74
+ # Get available profiles
75
+ profiles = get_available_profiles()
76
+ for name, data in profiles.items():
77
+ print(f"{name}: {data['description']}")
78
+
79
+ # Get a specific profile
80
+ technical_profile = get_profile("technical")
81
+ print(f"Temperature: {technical_profile['temperature']}")
82
+
83
+ # Set a profile
84
+ config = Config()
85
+ config.set_profile("creative", "local")
86
+ ```
87
+
88
+ ### Configuration Files
89
+
90
+ The configuration system uses two configuration files:
91
+
92
+ - Global configuration file: `~/.janito/config.json`
93
+ - Local configuration file: `.janito/config.json` (in the current workspace directory)
94
+
95
+ Local configuration overrides global configuration when both are present.
96
+
97
+ ## Extending the Configuration System
98
+
99
+ To add a new configuration property:
100
+
101
+ 1. Add a property getter and setter in `core/properties.py`
102
+ 2. Update the `_apply_config` method in `core/singleton.py` to handle the new property
103
+ 3. Add validation in `cli/validators.py` if needed
104
+ 4. Update the command handling in `cli/commands.py` to support the new property
@@ -0,0 +1,16 @@
1
+ """
2
+ Configuration module for Janito.
3
+ Provides a singleton Config class to access configuration values.
4
+ Supports both local and global configuration with merging functionality.
5
+ """
6
+ from .core.singleton import Config
7
+ from .profiles.definitions import PROFILES
8
+ from .profiles.manager import get_available_profiles, get_profile
9
+
10
+ # Convenience function to get the config instance
11
+ def get_config() -> Config:
12
+ """Get the singleton Config instance."""
13
+ return Config()
14
+
15
+ # Re-export the Config class for backward compatibility
16
+ __all__ = ["Config", "PROFILES", "get_config", "get_available_profiles", "get_profile"]
@@ -0,0 +1,28 @@
1
+ """
2
+ CLI integration for Janito configuration.
3
+ Provides command handling and validation for configuration-related CLI commands.
4
+ """
5
+ from .commands import (
6
+ handle_reset_config,
7
+ handle_reset_local_config,
8
+ handle_reset_global_config,
9
+ handle_show_config,
10
+ handle_set_api_key,
11
+ handle_set_local_config,
12
+ handle_set_global_config,
13
+ handle_config_commands
14
+ )
15
+ from .validators import validate_temperature, validate_boolean_value
16
+
17
+ __all__ = [
18
+ "handle_reset_config",
19
+ "handle_reset_local_config",
20
+ "handle_reset_global_config",
21
+ "handle_show_config",
22
+ "handle_set_api_key",
23
+ "handle_set_local_config",
24
+ "handle_set_global_config",
25
+ "handle_config_commands",
26
+ "validate_temperature",
27
+ "validate_boolean_value"
28
+ ]
@@ -0,0 +1,397 @@
1
+ """
2
+ Command handling functions for configuration-related CLI commands.
3
+ """
4
+ import sys
5
+ import os
6
+ from pathlib import Path
7
+ from typing import Optional, Dict, Any
8
+ import typer
9
+ from rich.console import Console
10
+
11
+ from ..core.singleton import Config
12
+ from .validators import validate_temperature, validate_boolean_value, validate_config_key_value
13
+
14
+ console = Console()
15
+
16
+ def handle_reset_config(reset_config: bool, ctx: typer.Context, query: Optional[str]) -> bool:
17
+ """
18
+ Handle the --reset-config parameter (deprecated, kept for backward compatibility).
19
+ This function now does nothing as --reset-config has been replaced by --reset-local-config and --reset-global-config.
20
+
21
+ Args:
22
+ reset_config: Whether to reset the configuration (ignored)
23
+ ctx: Typer context
24
+ query: Query string
25
+
26
+ Returns:
27
+ bool: Always returns False
28
+ """
29
+ # This function is kept for backward compatibility but does nothing
30
+ # Users should use --reset-local-config or --reset-global-config instead
31
+ return False
32
+
33
+ def handle_reset_local_config(reset_local_config: bool, ctx: typer.Context, query: Optional[str]) -> bool:
34
+ """
35
+ Handle the --reset-local-config parameter.
36
+ This removes the local configuration file (.janito/config.json) in the current workspace.
37
+
38
+ Args:
39
+ reset_local_config: Whether to reset the local configuration
40
+ ctx: Typer context
41
+ query: Query string
42
+
43
+ Returns:
44
+ bool: True if the program should exit after this operation
45
+ """
46
+ if reset_local_config:
47
+ try:
48
+ config_path = Path(Config().workspace_dir) / ".janito" / "config.json"
49
+ if Config().reset_local_config():
50
+ console.print(f"[bold green]✅ Local configuration reset[/bold green]")
51
+ else:
52
+ console.print(f"[bold yellow]⚠️ No local configuration found[/bold yellow]")
53
+ except Exception as e:
54
+ console.print(f"[bold red]Error removing configuration file:[/bold red] {str(e)}")
55
+
56
+ # Exit after resetting config if no other operation is requested
57
+ return ctx.invoked_subcommand is None and not query
58
+
59
+ return False
60
+
61
+ def handle_reset_global_config(reset_global_config: bool, ctx: typer.Context, query: Optional[str]) -> bool:
62
+ """
63
+ Handle the --reset-global-config parameter.
64
+ This removes the global configuration file (~/.janito/config.json) in the user's home directory.
65
+
66
+ Args:
67
+ reset_global_config: Whether to reset the global configuration
68
+ ctx: Typer context
69
+ query: Query string
70
+
71
+ Returns:
72
+ bool: True if the program should exit after this operation
73
+ """
74
+ if reset_global_config:
75
+ try:
76
+ config_path = Path.home() / ".janito" / "config.json"
77
+ if Config().reset_global_config():
78
+ console.print(f"[bold green]✅ Global configuration reset[/bold green]")
79
+ else:
80
+ console.print(f"[bold yellow]⚠️ No global configuration found[/bold yellow]")
81
+ except Exception as e:
82
+ console.print(f"[bold red]Error removing configuration file:[/bold red] {str(e)}")
83
+
84
+ # Exit after resetting config if no other operation is requested
85
+ return ctx.invoked_subcommand is None and not query
86
+
87
+ return False
88
+
89
+ def handle_show_config(show_config: bool, ctx: typer.Context, query: Optional[str]) -> bool:
90
+ """
91
+ Handle the --show-config parameter.
92
+
93
+ Args:
94
+ show_config: Whether to show the configuration
95
+ ctx: Typer context
96
+ query: Query string
97
+
98
+ Returns:
99
+ bool: True if the program should exit after this operation
100
+ """
101
+ if show_config:
102
+ config = Config()
103
+ console.print("[bold blue]⚙️ Current Configuration:[/bold blue]")
104
+
105
+ # Show configuration file paths
106
+ local_config_path = Path(config.workspace_dir) / ".janito" / "config.json"
107
+ global_config_path = Path.home() / ".janito" / "config.json"
108
+ console.print(f"[bold]📁 Local Configuration File:[/bold] {local_config_path}")
109
+ console.print(f"[bold]🏠 Global Configuration File:[/bold] {global_config_path}")
110
+
111
+ # Show API key status
112
+ api_key_global = Config().get_api_key()
113
+ if api_key_global:
114
+ console.print(f"[bold]🔑 API Key:[/bold] [green]Set in global config[/green]")
115
+ else:
116
+ console.print(f"[bold]🔑 API Key:[/bold] [red]Not set[/red]")
117
+
118
+ # Show merged configuration (effective settings)
119
+ console.print("\n[bold blue]🔄 Merged Configuration (Effective Settings):[/bold blue]")
120
+ console.print(f"[bold]🔊 Verbose Mode:[/bold] {'Enabled' if config.verbose else 'Disabled'}")
121
+ console.print(f"[bold]❓ Ask Mode:[/bold] {'Enabled' if config.ask_mode else 'Disabled'} [dim](runtime-only setting)[/dim]")
122
+ console.print(f"[bold]📊 Show Usage Report:[/bold] {'Enabled' if config.show_usage_report else 'Disabled'}")
123
+ console.print(f"[bold]👤 Role:[/bold] [bold white on blue]{config.role}[/bold white on blue]")
124
+ console.print(f"[bold]🌡️ Temperature:[/bold] {config.temperature}")
125
+
126
+ # Show profile information if one is set
127
+ if config.profile:
128
+ profile_data = config.get_available_profiles()[config.profile]
129
+ console.print(f"[bold]📋 Active Profile:[/bold] {config.profile} - {profile_data['description']}")
130
+
131
+ # Show local configuration
132
+ local_config = config.get_local_config()
133
+ if local_config:
134
+ console.print("\n[bold blue]📁 Local Configuration:[/bold blue]")
135
+ for key, value in local_config.items():
136
+ # Don't show API key or runtime-only settings like ask_mode
137
+ if key != "api_key" and key != "ask_mode":
138
+ console.print(f"[bold]🔹 {key}:[/bold] {value}")
139
+ else:
140
+ console.print("\n[bold blue]📁 Local Configuration:[/bold blue] [dim]Empty[/dim]")
141
+
142
+ # Show global configuration
143
+ global_config = config.get_global_config()
144
+ if global_config:
145
+ console.print("\n[bold blue]🏠 Global Configuration:[/bold blue]")
146
+ for key, value in global_config.items():
147
+ # Don't show API key or runtime-only settings like ask_mode
148
+ if key != "api_key" and key != "ask_mode":
149
+ console.print(f"[bold]🔹 {key}:[/bold] {value}")
150
+ else:
151
+ console.print("\n[bold blue]🏠 Global Configuration:[/bold blue] [dim]Empty[/dim]")
152
+
153
+ # Show available profiles
154
+ profiles = config.get_available_profiles()
155
+ if profiles:
156
+ console.print("\n[bold blue]📋 Available Parameter Profiles:[/bold blue]")
157
+ for name, data in profiles.items():
158
+ console.print(f"[bold]🔹 {name}[/bold] - {data['description']}")
159
+
160
+ # Exit if this was the only operation requested
161
+ return ctx.invoked_subcommand is None and not query
162
+
163
+ return False
164
+
165
+ def handle_set_api_key(set_api_key: Optional[str], ctx: typer.Context, query: Optional[str]) -> bool:
166
+ """
167
+ Handle the --set-api-key parameter.
168
+
169
+ Args:
170
+ set_api_key: API key
171
+ ctx: Typer context
172
+ query: Query string
173
+
174
+ Returns:
175
+ bool: True if the program should exit after this operation
176
+ """
177
+ if set_api_key is not None:
178
+ try:
179
+ Config().set_api_key(set_api_key)
180
+ console.print(f"[bold green]✅ API key saved[/bold green]")
181
+
182
+ # Exit after setting API key if no other operation is requested
183
+ return ctx.invoked_subcommand is None and not query
184
+ except Exception as e:
185
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
186
+ sys.exit(1)
187
+
188
+ return False
189
+
190
+ def _handle_config_setting(key: str, value: str, config_type: str = "local") -> bool:
191
+ """
192
+ Handle setting a configuration value.
193
+
194
+ Args:
195
+ key: Configuration key
196
+ value: Configuration value
197
+ config_type: Type of configuration to update ("local" or "global")
198
+
199
+ Returns:
200
+ bool: True if the operation was successful
201
+ """
202
+ try:
203
+ if key == "profile":
204
+ try:
205
+ Config().set_profile(value, config_type)
206
+ profile_data = Config().get_available_profiles()[value.lower()]
207
+ console.print(f"[bold green]✅ Profile set to '{value.lower()}'[/bold green]")
208
+ return True
209
+ except ValueError as e:
210
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
211
+ return False
212
+ elif key == "temperature":
213
+ is_valid, result = validate_temperature(value)
214
+ if not is_valid:
215
+ console.print(f"[bold red]Error:[/bold red] {result}")
216
+ return False
217
+
218
+ if config_type == "local":
219
+ Config().temperature = result, "local"
220
+ else:
221
+ Config().temperature = result, "global"
222
+ console.print(f"[bold green]✅ Temperature set to {result}[/bold green]")
223
+ return True
224
+ # top_k and top_p are now only accessible through profiles
225
+ elif key == "role":
226
+ if config_type == "local":
227
+ Config().role = value, "local"
228
+ else:
229
+ Config().role = value, "global"
230
+ console.print(f"[bold green]✅ Role set to '{value}'[/bold green]")
231
+ return True
232
+ elif key == "ask_mode":
233
+ is_valid, result = validate_boolean_value(value)
234
+ if not is_valid:
235
+ console.print(f"[bold red]Error:[/bold red] {result}")
236
+ return False
237
+
238
+ # ask_mode is a runtime-only setting, inform the user
239
+ console.print(f"[bold yellow]⚠️ Ask mode is a runtime-only setting and cannot be stored in configuration.[/bold yellow]")
240
+ console.print(f"[bold yellow]Use the --ask flag when running the command instead.[/bold yellow]")
241
+ return True
242
+ elif key == "show_usage_report":
243
+ is_valid, result = validate_boolean_value(value)
244
+ if not is_valid:
245
+ console.print(f"[bold red]Error:[/bold red] {result}")
246
+ return False
247
+
248
+ if config_type == "local":
249
+ Config().show_usage_report = result, "local"
250
+ else:
251
+ Config().show_usage_report = result, "global"
252
+ console.print(f"[bold green]✅ Show usage report set to {result}[/bold green]")
253
+ return True
254
+ else:
255
+ # For other keys, set them directly in the configuration
256
+ if config_type == "local":
257
+ Config().set_local_config(key, value)
258
+ else:
259
+ Config().set_global_config(key, value)
260
+ console.print(f"[bold green]✅ {key} set to '{value}'[/bold green]")
261
+ return True
262
+ except Exception as e:
263
+ console.print(f"[bold red]Error:[/bold red] {str(e)}")
264
+ return False
265
+
266
+
267
+ def handle_set_local_config(config_str: Optional[str], ctx: typer.Context, query: Optional[str]) -> bool:
268
+ """
269
+ Handle the --set-local-config parameter.
270
+
271
+ Args:
272
+ config_str: Configuration string in format 'key=value'
273
+ ctx: Typer context
274
+ query: Query string
275
+
276
+ Returns:
277
+ bool: True if the program should exit after this operation
278
+ """
279
+ if config_str is not None:
280
+ is_valid, result = validate_config_key_value(config_str)
281
+ if not is_valid:
282
+ console.print(f"[bold red]Error:[/bold red] {result}")
283
+ return ctx.invoked_subcommand is None and not query
284
+
285
+ key, value = result
286
+ _handle_config_setting(key, value, "local")
287
+
288
+ # Exit after applying config changes if no other operation is requested
289
+ return ctx.invoked_subcommand is None and not query
290
+
291
+ return False
292
+
293
+ def handle_set_global_config(config_str: Optional[str], ctx: typer.Context, query: Optional[str]) -> bool:
294
+ """
295
+ Handle the --set-global-config parameter.
296
+
297
+ Args:
298
+ config_str: Configuration string in format 'key=value'
299
+ ctx: Typer context
300
+ query: Query string
301
+
302
+ Returns:
303
+ bool: True if the program should exit after this operation
304
+ """
305
+ if config_str is not None:
306
+ is_valid, result = validate_config_key_value(config_str)
307
+ if not is_valid:
308
+ console.print(f"[bold red]Error:[/bold red] {result}")
309
+ return ctx.invoked_subcommand is None and not query
310
+
311
+ key, value = result
312
+ _handle_config_setting(key, value, "global")
313
+
314
+ # Exit after applying config changes if no other operation is requested
315
+ return ctx.invoked_subcommand is None and not query
316
+
317
+ return False
318
+
319
+ def handle_config_commands(
320
+ ctx: typer.Context,
321
+ reset_config: bool,
322
+ reset_local_config: bool = False,
323
+ reset_global_config: bool = False,
324
+ workspace: Optional[str] = None,
325
+ show_config: bool = False,
326
+ profile: Optional[str] = None,
327
+ role: Optional[str] = None,
328
+ set_api_key: Optional[str] = None,
329
+ set_local_config: Optional[str] = None,
330
+ set_global_config: Optional[str] = None,
331
+ query: Optional[str] = None,
332
+ continue_flag: Optional[str] = None,
333
+ history_flag: bool = False,
334
+ history_count: Optional[int] = None
335
+ ) -> bool:
336
+ """
337
+ Handle all configuration-related commands.
338
+
339
+ Args:
340
+ ctx: Typer context
341
+ reset_config: Deprecated parameter kept for backward compatibility
342
+ reset_local_config: Whether to reset the local configuration
343
+ reset_global_config: Whether to reset the global configuration
344
+ workspace: Workspace directory path
345
+ show_config: Whether to show the configuration
346
+ profile: Profile name
347
+ role: Role name
348
+ set_api_key: API key
349
+ set_local_config: Configuration string in format 'key=value' for local config
350
+ set_global_config: Configuration string in format 'key=value' for global config
351
+ query: Query string
352
+ continue_flag: Optional string that can be empty (flag only) or contain a chat ID
353
+ history_flag: Whether to show conversation history (--history flag)
354
+ history_count: Number of history entries to display (value after --history)
355
+
356
+ Returns:
357
+ bool: True if the program should exit after these operations
358
+ """
359
+ # Import these here to avoid circular imports
360
+ from janito.cli.commands.workspace import handle_workspace
361
+ from janito.cli.commands.profile import handle_profile, handle_role
362
+ from janito.cli.commands.history import handle_history
363
+
364
+ # Handle each command and check if we should exit after it
365
+ if handle_reset_config(reset_config, ctx, query):
366
+ return True
367
+
368
+ if handle_reset_local_config(reset_local_config, ctx, query):
369
+ return True
370
+
371
+ if handle_reset_global_config(reset_global_config, ctx, query):
372
+ return True
373
+
374
+ handle_workspace(workspace)
375
+
376
+ if handle_show_config(show_config, ctx, query):
377
+ return True
378
+
379
+ if handle_profile(profile, ctx, query):
380
+ return True
381
+
382
+ if handle_role(role, ctx, query):
383
+ return True
384
+
385
+ if handle_set_api_key(set_api_key, ctx, query):
386
+ return True
387
+
388
+ if handle_set_local_config(set_local_config, ctx, query):
389
+ return True
390
+
391
+ if handle_set_global_config(set_global_config, ctx, query):
392
+ return True
393
+
394
+ if handle_history(history_flag, history_count, ctx, query):
395
+ return True
396
+
397
+ return False
@@ -0,0 +1,77 @@
1
+ """
2
+ Validation functions for configuration-related CLI commands.
3
+ """
4
+ from typing import Tuple, Any, Union, Optional
5
+
6
+ def validate_temperature(value: str) -> Tuple[bool, Union[float, str]]:
7
+ """
8
+ Validate a temperature value from a string input.
9
+
10
+ Args:
11
+ value: String representation of a temperature value
12
+
13
+ Returns:
14
+ Tuple of (is_valid, result)
15
+ If valid, result is the float value
16
+ If invalid, result is an error message
17
+ """
18
+ try:
19
+ temp_value = float(value)
20
+ if temp_value < 0.0 or temp_value > 1.0:
21
+ return False, "Temperature must be between 0.0 and 1.0"
22
+ return True, temp_value
23
+ except ValueError:
24
+ return False, f"Invalid temperature value: {value}. Must be a float between 0.0 and 1.0."
25
+
26
+ def validate_boolean_value(value: str) -> Tuple[bool, Union[bool, str]]:
27
+ """
28
+ Validate a boolean value from a string input.
29
+
30
+ Args:
31
+ value: String representation of a boolean value
32
+
33
+ Returns:
34
+ Tuple of (is_valid, result)
35
+ If valid, result is the boolean value
36
+ If invalid, result is an error message
37
+ """
38
+ try:
39
+ lower_value = value.lower()
40
+ if lower_value in ["true", "yes", "1", "on", "y"]:
41
+ return True, True
42
+ elif lower_value in ["false", "no", "0", "off", "n"]:
43
+ return True, False
44
+ else:
45
+ return False, f"Invalid boolean value: {value}. Use 'true', 'false', 'yes', 'no', '1', '0', 'on', or 'off'."
46
+ except Exception:
47
+ return False, f"Invalid boolean value: {value}. Use 'true', 'false', 'yes', 'no', '1', '0', 'on', or 'off'."
48
+
49
+ def validate_config_key_value(config_str: str) -> Tuple[bool, Union[Tuple[str, str], str]]:
50
+ """
51
+ Validate a configuration key-value pair from a string input.
52
+
53
+ Args:
54
+ config_str: String in the format 'key=value'
55
+
56
+ Returns:
57
+ Tuple of (is_valid, result)
58
+ If valid, result is a tuple of (key, value)
59
+ If invalid, result is an error message
60
+ """
61
+ try:
62
+ # Parse the config string
63
+ config_parts = config_str.split("=", 1)
64
+ if len(config_parts) != 2:
65
+ return False, "Invalid configuration format. Use 'key=value' format."
66
+
67
+ key = config_parts[0].strip()
68
+ value = config_parts[1].strip()
69
+
70
+ # Remove quotes if present
71
+ if (value.startswith("'") and value.endswith("'")) or \
72
+ (value.startswith('"') and value.endswith('"')):
73
+ value = value[1:-1]
74
+
75
+ return True, (key, value)
76
+ except Exception as e:
77
+ return False, f"Invalid configuration format: {str(e)}"