auto-coder 0.1.354__py3-none-any.whl → 0.1.356__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 auto-coder might be problematic. Click here for more details.

Files changed (40) hide show
  1. {auto_coder-0.1.354.dist-info → auto_coder-0.1.356.dist-info}/METADATA +1 -1
  2. {auto_coder-0.1.354.dist-info → auto_coder-0.1.356.dist-info}/RECORD +40 -35
  3. autocoder/agent/agentic_filter.py +1 -1
  4. autocoder/agent/auto_learn.py +631 -0
  5. autocoder/auto_coder.py +8 -0
  6. autocoder/auto_coder_runner.py +59 -87
  7. autocoder/chat/conf_command.py +270 -0
  8. autocoder/chat/models_command.py +485 -0
  9. autocoder/chat/rules_command.py +458 -0
  10. autocoder/chat_auto_coder.py +34 -24
  11. autocoder/chat_auto_coder_lang.py +156 -2
  12. autocoder/commands/auto_command.py +1 -1
  13. autocoder/commands/auto_web.py +1 -1
  14. autocoder/common/__init__.py +2 -0
  15. autocoder/common/auto_coder_lang.py +9 -1
  16. autocoder/common/command_completer.py +58 -12
  17. autocoder/common/command_completer_v2.py +615 -0
  18. autocoder/common/global_cancel.py +53 -16
  19. autocoder/common/rulefiles/autocoderrules_utils.py +83 -0
  20. autocoder/common/v2/agent/agentic_edit.py +4 -4
  21. autocoder/common/v2/code_agentic_editblock_manager.py +9 -9
  22. autocoder/common/v2/code_diff_manager.py +2 -2
  23. autocoder/common/v2/code_editblock_manager.py +11 -10
  24. autocoder/common/v2/code_strict_diff_manager.py +3 -2
  25. autocoder/dispacher/actions/action.py +6 -6
  26. autocoder/dispacher/actions/plugins/action_regex_project.py +2 -2
  27. autocoder/events/event_manager_singleton.py +1 -1
  28. autocoder/index/index.py +2 -2
  29. autocoder/rag/cache/local_byzer_storage_cache.py +1 -1
  30. autocoder/rag/cache/local_duckdb_storage_cache.py +8 -0
  31. autocoder/rag/loaders/image_loader.py +25 -13
  32. autocoder/rag/long_context_rag.py +2 -2
  33. autocoder/utils/auto_coder_utils/chat_stream_out.py +3 -4
  34. autocoder/utils/model_provider_selector.py +14 -2
  35. autocoder/utils/thread_utils.py +9 -27
  36. autocoder/version.py +1 -1
  37. {auto_coder-0.1.354.dist-info → auto_coder-0.1.356.dist-info}/LICENSE +0 -0
  38. {auto_coder-0.1.354.dist-info → auto_coder-0.1.356.dist-info}/WHEEL +0 -0
  39. {auto_coder-0.1.354.dist-info → auto_coder-0.1.356.dist-info}/entry_points.txt +0 -0
  40. {auto_coder-0.1.354.dist-info → auto_coder-0.1.356.dist-info}/top_level.txt +0 -0
@@ -34,7 +34,7 @@ from rich.console import Console
34
34
  from rich.panel import Panel
35
35
  from rich.table import Table
36
36
  from rich.live import Live
37
- from rich.text import Text
37
+ # Removed Text import as it's only used in the deleted print_conf
38
38
  from rich.live import Live
39
39
  from rich.markdown import Markdown
40
40
  from byzerllm.utils.nontext import Image
@@ -53,6 +53,7 @@ from autocoder.common.memory_manager import get_global_memory_file_paths
53
53
  from autocoder import models as models_module
54
54
  import shlex
55
55
  from autocoder.utils.llms import get_single_llm
56
+ import fnmatch
56
57
  import pkg_resources
57
58
  from autocoder.common.printer import Printer
58
59
  from autocoder.utils.thread_utils import run_in_raw_thread
@@ -111,6 +112,7 @@ commands = [
111
112
  "/chat",
112
113
  "/ask",
113
114
  "/commit",
115
+ "/rules",
114
116
  "/revert",
115
117
  "/index/query",
116
118
  "/index/build",
@@ -437,18 +439,10 @@ def initialize_system(args:InitializeSystemRequest):
437
439
  configure_success[0] = True
438
440
 
439
441
  if first_time[0] and args.product_mode == "pro" and configure_success[0]:
440
- configure(f"model:v3_chat", skip_print=True)
441
- configure(f"chat_model:r1_chat", skip_print=True)
442
- configure(f"generate_rerank_model:r1_chat", skip_print=True)
443
- configure(f"code_model:v3_chat", skip_print=True)
444
- configure(f"index_filter_model:r1_chat", skip_print=True)
442
+ configure(f"model:v3_chat", skip_print=True)
445
443
 
446
444
  if first_time[0] and args.product_mode == "lite" and models_module.check_model_exists("v3_chat"):
447
- configure(f"model:v3_chat", skip_print=True)
448
- configure(f"chat_model:r1_chat", skip_print=True)
449
- configure(f"generate_rerank_model:r1_chat", skip_print=True)
450
- configure(f"code_model:v3_chat", skip_print=True)
451
- configure(f"index_filter_model:r1_chat", skip_print=True)
445
+ configure(f"model:v3_chat", skip_print=True)
452
446
 
453
447
 
454
448
  def convert_yaml_config_to_str(yaml_config):
@@ -541,7 +535,7 @@ def find_files_in_project(patterns: List[str]) -> List[str]:
541
535
  def convert_config_value(key, value):
542
536
  field_info = AutoCoderArgs.model_fields.get(key)
543
537
  if field_info:
544
- if value.lower() in ["true", "false"]:
538
+ if isinstance(value, str) and value.lower() in ["true", "false"]:
545
539
  return value.lower() == "true"
546
540
  elif "int" in str(field_info.annotation):
547
541
  return int(value)
@@ -652,15 +646,15 @@ def load_memory():
652
646
  _memory = json.load(f)
653
647
  # clear memory
654
648
  memory.clear()
655
- memory.update(_memory)
656
- completer.update_current_files(memory["current_files"]["files"])
649
+ memory.update(_memory)
657
650
  return memory
658
651
 
659
652
  def get_memory():
660
653
  return load_memory()
661
654
 
662
655
 
663
- completer = CommandCompleter(commands,
656
+ from autocoder.common.command_completer_v2 import CommandCompleterV2
657
+ completer = CommandCompleterV2(commands,
664
658
  file_system_model=CCFileSystemModel(project_root=project_root,
665
659
  defaut_exclude_dirs=defaut_exclude_dirs,
666
660
  get_all_file_names_in_project=get_all_file_names_in_project,
@@ -669,57 +663,8 @@ completer = CommandCompleter(commands,
669
663
  get_all_file_in_project_with_dot=get_all_file_in_project_with_dot,
670
664
  get_symbol_list=get_symbol_list
671
665
  ),
672
- memory_model=CCMemoryModel(memory=memory,
673
- save_memory_func=save_memory))
674
-
675
-
676
-
677
-
678
- def print_conf(content:Dict[str,Any]):
679
- """Display configuration dictionary in a Rich table format with enhanced visual styling.
680
-
681
- Args:
682
- conf (Dict[str, Any]): Configuration dictionary to display
683
- """
684
- console = Console()
685
-
686
- # Create a styled table with rounded borders
687
- table = Table(
688
- show_header=True,
689
- header_style="bold magenta",
690
- title=get_message("conf_title"),
691
- title_style="bold blue",
692
- border_style="blue",
693
- show_lines=True
694
- )
695
-
696
- # Add columns with explicit width and alignment
697
- table.add_column(get_message("conf_key"), style="cyan", justify="right", width=30, no_wrap=False)
698
- table.add_column(get_message("conf_value"), style="green", justify="left", width=50, no_wrap=False)
699
-
700
- # Sort keys for consistent display
701
- for key in sorted(content.keys()):
702
- value = content[key]
703
- # Format value based on type
704
- if isinstance(value, (dict, list)):
705
- formatted_value = Text(json.dumps(value, indent=2), style="yellow")
706
- elif isinstance(value, bool):
707
- formatted_value = Text(str(value), style="bright_green" if value else "red")
708
- elif isinstance(value, (int, float)):
709
- formatted_value = Text(str(value), style="bright_cyan")
710
- else:
711
- formatted_value = Text(str(value), style="green")
712
-
713
- table.add_row(str(key), formatted_value)
714
-
715
- # Add padding and print with a panel
716
- console.print(Panel(
717
- table,
718
- padding=(1, 2),
719
- subtitle=f"[italic]{get_message('conf_subtitle')}[/italic]",
720
- border_style="blue"
721
- ))
722
-
666
+ memory_model=CCMemoryModel(get_memory_func=get_memory,
667
+ save_memory_func=save_memory))
723
668
  def revert():
724
669
  result_manager = ResultManager()
725
670
  last_yaml_file = get_last_yaml_file("actions")
@@ -1025,7 +970,6 @@ def add_files(args: List[str]):
1025
970
  result_manager.append(content=f"No files matched.",
1026
971
  meta={"action": "add_files","success":False, "input":{ "args": args}})
1027
972
 
1028
- completer.update_current_files(memory["current_files"]["files"])
1029
973
  save_memory()
1030
974
 
1031
975
 
@@ -1038,39 +982,61 @@ def remove_files(file_names: List[str]):
1038
982
  memory["current_files"]["files"] = []
1039
983
  memory["current_files"]["current_groups"] = []
1040
984
  printer.print_in_terminal("remove_files_all", style="green")
1041
- result_manager.append(content="All files removed.",
985
+ result_manager.append(content="All files removed.",
1042
986
  meta={"action": "remove_files","success":True, "input":{ "file_names": file_names}})
1043
987
  else:
1044
- removed_files = []
1045
- for file in memory["current_files"]["files"]:
1046
- if os.path.basename(file) in file_names:
1047
- removed_files.append(file)
1048
- elif file in file_names:
1049
- removed_files.append(file)
1050
- for file in removed_files:
1051
- memory["current_files"]["files"].remove(file)
1052
-
1053
- if removed_files:
1054
- table = Table(
1055
- show_header=True,
988
+ files_to_remove = set()
989
+ current_files_abs = memory["current_files"]["files"]
990
+
991
+ for pattern in file_names:
992
+ pattern = pattern.strip() # Remove leading/trailing whitespace
993
+ if not pattern:
994
+ continue
995
+
996
+ is_wildcard = "*" in pattern or "?" in pattern
997
+
998
+ for file_path_abs in current_files_abs:
999
+ relative_path = os.path.relpath(file_path_abs, project_root)
1000
+ basename = os.path.basename(file_path_abs)
1001
+
1002
+ matched = False
1003
+ if is_wildcard:
1004
+ # Match pattern against relative path or basename
1005
+ if fnmatch.fnmatch(relative_path, pattern) or fnmatch.fnmatch(basename, pattern):
1006
+ matched = True
1007
+ else:
1008
+ # Exact match against relative path, absolute path, or basename
1009
+ if relative_path == pattern or file_path_abs == pattern or basename == pattern:
1010
+ matched = True
1011
+
1012
+ if matched:
1013
+ files_to_remove.add(file_path_abs)
1014
+
1015
+ removed_files_list = list(files_to_remove)
1016
+ if removed_files_list:
1017
+ # Update memory by filtering out the files to remove
1018
+ memory["current_files"]["files"] = [
1019
+ f for f in current_files_abs if f not in files_to_remove
1020
+ ]
1021
+
1022
+ table = Table(
1023
+ show_header=True,
1056
1024
  header_style="bold magenta"
1057
1025
  )
1058
- table.add_column("File", style="green")
1059
- for f in removed_files:
1026
+ table.add_column(printer.get_message_from_key("file_column_title"), style="green")
1027
+ for f in removed_files_list:
1060
1028
  table.add_row(os.path.relpath(f, project_root))
1061
1029
 
1062
1030
  console = Console()
1063
1031
  console.print(
1064
1032
  Panel(table, border_style="green",
1065
- title=printer.get_message_from_key("files_removed")))
1066
- result_manager.append(content=f"Removed files: {', '.join(removed_files)}",
1033
+ title=printer.get_message_from_key("files_removed")))
1034
+ result_manager.append(content=f"Removed files: {', '.join(removed_files_list)}",
1067
1035
  meta={"action": "remove_files","success":True, "input":{ "file_names": file_names}})
1068
1036
  else:
1069
1037
  printer.print_in_terminal("remove_files_none", style="yellow")
1070
1038
  result_manager.append(content=printer.get_message_from_key("remove_files_none"),
1071
- meta={"action": "remove_files","success":False, "input":{ "file_names": file_names}})
1072
-
1073
- completer.update_current_files(memory["current_files"]["files"])
1039
+ meta={"action": "remove_files","success":False, "input":{ "file_names": file_names}})
1074
1040
  save_memory()
1075
1041
 
1076
1042
  @run_in_raw_thread()
@@ -1644,6 +1610,12 @@ def coding(query: str):
1644
1610
  save_memory()
1645
1611
  completer.refresh_files()
1646
1612
 
1613
+ @run_in_raw_thread()
1614
+ def rules(query: str):
1615
+ from autocoder.chat.rules_command import handle_rules_command
1616
+ memory = get_memory()
1617
+ handle_rules_command(query, memory,coding_func=coding)
1618
+ completer.refresh_files()
1647
1619
 
1648
1620
  @byzerllm.prompt()
1649
1621
  def code_review(query: str) -> str:
@@ -0,0 +1,270 @@
1
+
2
+ import os
3
+ import io
4
+ import contextlib
5
+ import fnmatch
6
+ import json
7
+ from typing import Dict, Any, List, Callable, Optional
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+ from rich.text import Text
12
+ from autocoder.auto_coder_runner import save_memory # Import save_memory
13
+ from autocoder.common.conf_validator import ConfigValidator
14
+ from autocoder.common.auto_coder_lang import get_message, get_message_with_format
15
+
16
+ # Helper function to print the configuration table (internal implementation)
17
+ def _print_conf_table(content: Dict[str, Any], title: str = "Configuration Settings"):
18
+ """Display configuration dictionary in a Rich table format."""
19
+ console = Console(file=io.StringIO(), force_terminal=True, color_system="truecolor") # Capture output
20
+
21
+ # Create a styled table with rounded borders
22
+ table = Table(
23
+ show_header=True,
24
+ header_style="bold magenta",
25
+ title=title,
26
+ title_style="bold blue",
27
+ border_style="blue",
28
+ show_lines=True
29
+ )
30
+
31
+ # Add columns with explicit width and alignment
32
+ table.add_column(get_message("conf_key"), style="cyan", justify="right", width=30, no_wrap=False)
33
+ table.add_column(get_message("conf_value"), style="green", justify="left", width=50, no_wrap=False)
34
+
35
+ # Sort keys for consistent display
36
+ for key in sorted(content.keys()):
37
+ value = content[key]
38
+ # Format value based on type
39
+ if isinstance(value, (dict, list)):
40
+ formatted_value = Text(json.dumps(value, indent=2), style="yellow")
41
+ elif isinstance(value, bool):
42
+ formatted_value = Text(str(value), style="bright_green" if value else "red")
43
+ elif isinstance(value, (int, float)):
44
+ formatted_value = Text(str(value), style="bright_cyan")
45
+ else:
46
+ formatted_value = Text(str(value), style="green")
47
+
48
+ table.add_row(str(key), formatted_value)
49
+
50
+ # Add padding and print with a panel
51
+ console.print(Panel(
52
+ table,
53
+ padding=(1, 2),
54
+ subtitle=f"[italic]{get_message('conf_subtitle')}[/italic]",
55
+ border_style="blue"
56
+ ))
57
+ return console.file.getvalue() # Return captured string
58
+
59
+ # --- Command Handlers ---
60
+
61
+ def _handle_list_conf(memory: Dict[str, Any], args: List[str]) -> str:
62
+ """Handles listing configuration settings, supports wildcard filtering."""
63
+ conf = memory.get("conf", {})
64
+ pattern = args[0] if args else "*" # Default to all if no pattern
65
+
66
+ if pattern == "*":
67
+ title = get_message("conf_title")
68
+ filtered_conf = conf
69
+ else:
70
+ title = f"Filtered Configuration (Pattern: {pattern})"
71
+ filtered_conf = {k: v for k, v in conf.items() if fnmatch.fnmatch(k, pattern)}
72
+ if not filtered_conf:
73
+ return f"No configuration keys found matching pattern: {pattern}"
74
+
75
+ if not filtered_conf and pattern == "*":
76
+ return "No configurations set."
77
+
78
+ return _print_conf_table(filtered_conf, title)
79
+
80
+
81
+ def _handle_get_conf(memory: Dict[str, Any], args: List[str]) -> str:
82
+ """Handles getting a specific configuration setting."""
83
+ if len(args) != 1:
84
+ return "Error: 'get' command requires exactly one argument (the key). Usage: /conf get <key>"
85
+ key = args[0]
86
+ conf = memory.get("conf", {})
87
+ value = conf.get(key)
88
+ if value is None:
89
+ return f"Error: Configuration key '{key}' not found."
90
+ else:
91
+ # Format value for better readability
92
+ if isinstance(value, (list, dict)):
93
+ formatted_value = json.dumps(value, indent=2)
94
+ else:
95
+ formatted_value = repr(value)
96
+ return f"{key}: {formatted_value}"
97
+
98
+ def _parse_value(value_str: str) -> Any:
99
+ """Attempts to parse the value string into common types."""
100
+ value_str = value_str.strip()
101
+ if value_str.lower() == 'true':
102
+ return True
103
+ if value_str.lower() == 'false':
104
+ return False
105
+ if value_str.lower() == 'none' or value_str.lower() == 'null':
106
+ return None
107
+ # Keep quoted strings as strings without quotes
108
+ if (value_str.startswith('"') and value_str.endswith('"')) or \
109
+ (value_str.startswith("'") and value_str.endswith("'")):
110
+ return value_str[1:-1]
111
+
112
+ try:
113
+ # Try int first
114
+ return int(value_str)
115
+ except ValueError:
116
+ pass
117
+ try:
118
+ # Then try float
119
+ return float(value_str)
120
+ except ValueError:
121
+ pass
122
+ # If none of the above, return as string
123
+ return value_str
124
+
125
+ def _handle_set_conf(memory: Dict[str, Any], args: List[str]) -> str:
126
+ """Handles setting or updating a configuration setting."""
127
+ if len(args) < 2:
128
+ return "Error: 'set' command requires at least two arguments (key and value). Usage: /conf set <key> <value>"
129
+ key = args[0]
130
+ # Join the rest of the arguments to form the value string
131
+ value_str = " ".join(args[1:])
132
+ try:
133
+ parsed_value = _parse_value(value_str)
134
+
135
+ # Validate before setting
136
+ product_mode = memory.get("conf", {}).get("product_mode", "lite")
137
+ ConfigValidator.validate(key, str(parsed_value), product_mode) # Validate the parsed value as string initially if needed, or adjust validation
138
+
139
+ if "conf" not in memory:
140
+ memory["conf"] = {}
141
+ memory["conf"][key] = parsed_value
142
+ save_memory() # Save after modification
143
+ # Use repr for confirmation message for clarity
144
+ return f"Configuration updated: {key} = {repr(parsed_value)}"
145
+ except Exception as e:
146
+ return f"Error setting configuration for key '{key}': {e}"
147
+
148
+ def _handle_delete_conf(memory: Dict[str, Any], args: List[str]) -> str:
149
+ """Handles deleting a configuration setting."""
150
+ if len(args) != 1:
151
+ return "Error: 'delete' command requires exactly one argument (the key). Usage: /conf delete <key>"
152
+ key = args[0]
153
+ conf = memory.get("conf", {})
154
+ if key in conf:
155
+ try:
156
+ del memory["conf"][key]
157
+ save_memory() # Save after modification
158
+ return f"Configuration deleted: {key}"
159
+ except Exception as e:
160
+ return f"Error deleting key '{key}': {e}"
161
+ else:
162
+ return f"Error: Configuration key '{key}' not found."
163
+
164
+
165
+ def _handle_help(memory: Dict[str, Any], args: List[str]) -> str:
166
+ """Provides help text for the /conf command."""
167
+ if args:
168
+ return f"Error: 'help' command takes no arguments. Usage: /conf help"
169
+
170
+ help_text = """
171
+ /conf command usage:
172
+ /conf [pattern] - Show configurations. Optional wildcard pattern (e.g., *_model, api*).
173
+ /conf get <key> - Get the value of a specific configuration key.
174
+ /conf set <key>:<value> - Set or update a configuration key.
175
+ Value parsed (bool, int, float, None) or treated as string.
176
+ Use quotes ("value with spaces") for explicit strings.
177
+ /conf /drop <key> - Delete a configuration key.
178
+ /conf /export <path> - Export current configuration to a file.
179
+ /conf /import <path> - Import configuration from a file.
180
+ /conf help - Show this help message.
181
+ """
182
+ return help_text.strip()
183
+
184
+ # Command dispatch table
185
+ COMMAND_HANDLERS: Dict[str, Callable[[Dict[str, Any], List[str]], str]] = {
186
+ "list": _handle_list_conf,
187
+ "show": _handle_list_conf, # Alias
188
+ "get": _handle_get_conf,
189
+ "set": _handle_set_conf,
190
+ "delete": _handle_delete_conf,
191
+ "del": _handle_delete_conf, # Alias
192
+ "rm": _handle_delete_conf, # Alias
193
+ "help": _handle_help,
194
+ }
195
+
196
+ def handle_conf_command(command_args: str, memory: Dict[str, Any]) -> str:
197
+ """
198
+ Handles the /conf command, its subcommands, and wildcard listing.
199
+
200
+ Args:
201
+ command_args: The arguments string following the /conf command.
202
+ Example: "key:value", "get key", "set key value", "*_model", "/export path"
203
+ memory: The current session memory dictionary.
204
+
205
+ Returns:
206
+ A string response to be displayed to the user.
207
+ """
208
+ conf_str = command_args.strip()
209
+
210
+ # Handle special subcommands first
211
+ if conf_str.startswith("/export"):
212
+ from autocoder.common.conf_import_export import export_conf
213
+ export_path = conf_str[len("/export"):].strip()
214
+ if not export_path:
215
+ return "Error: Please specify a path for export. Usage: /conf /export <path>"
216
+ try:
217
+ export_conf(os.getcwd(), export_path)
218
+ return f"Configuration exported successfully to {export_path}"
219
+ except Exception as e:
220
+ return f"Error exporting configuration: {e}"
221
+ elif conf_str.startswith("/import"):
222
+ from autocoder.common.conf_import_export import import_conf
223
+ import_path = conf_str[len("/import"):].strip()
224
+ if not import_path:
225
+ return "Error: Please specify a path for import. Usage: /conf /import <path>"
226
+ try:
227
+ import_conf(os.getcwd(), import_path)
228
+ # Reload memory after import might be needed depending on import_conf implementation
229
+ # load_memory() # Consider if import_conf modifies the passed memory or global state
230
+ return f"Configuration imported successfully from {import_path}. Use '/conf' to see changes."
231
+ except Exception as e:
232
+ return f"Error importing configuration: {e}"
233
+
234
+ # Handle regular commands and listing/filtering
235
+ args = conf_str.split()
236
+
237
+ if not args:
238
+ # Default action: list all configurations
239
+ return _handle_list_conf(memory, [])
240
+ else:
241
+ command = args[0].lower()
242
+ command_args_list = args[1:]
243
+
244
+ # Check if the first argument is a known command or potentially a pattern
245
+ handler = COMMAND_HANDLERS.get(command)
246
+
247
+ if handler:
248
+ # It's a known command (list, get, set, delete, help)
249
+ try:
250
+ return handler(memory, command_args_list)
251
+ except Exception as e:
252
+ return f"An unexpected error occurred while executing '/conf {command}': {e}"
253
+ elif "*" in command or "?" in command:
254
+ # Treat as a list/filter pattern if it contains wildcards and is not a known command
255
+ return _handle_list_conf(memory, [command]) # Pass the pattern as the argument to list
256
+ else:
257
+ # Handle legacy key:value format for setting (optional, can be removed if only set command is preferred)
258
+ if ":" in conf_str and len(args) == 1: # Check if it looks like key:value and is a single "word"
259
+ parts = conf_str.split(":", 1)
260
+ if len(parts) == 2:
261
+ key, value_str = parts[0].strip(), parts[1].strip()
262
+ if key and value_str:
263
+ return _handle_set_conf(memory, [key, value_str])
264
+ else:
265
+ return f"Error: Invalid key:value format in '{conf_str}'. Use '/conf set {key} {value_str}' or '/conf help'."
266
+ else:
267
+ return f"Error: Unknown command or invalid format '{conf_str}'. Type '/conf help' for available commands."
268
+ else:
269
+ # If it's not a known command, not a wildcard, and not key:value format
270
+ return f"Error: Unknown command '/conf {command}'. Type '/conf help' for available commands."