yaicli 0.1.0__py3-none-any.whl → 0.2.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.
yaicli/config.py CHANGED
@@ -1,65 +1,19 @@
1
1
  import configparser
2
2
  from os import getenv
3
- from pathlib import Path
4
3
  from typing import Optional
5
4
 
6
5
  from rich import get_console
7
6
  from rich.console import Console
8
7
 
8
+ from yaicli.const import (
9
+ CONFIG_PATH,
10
+ DEFAULT_CHAT_HISTORY_DIR,
11
+ DEFAULT_CONFIG_INI,
12
+ DEFAULT_CONFIG_MAP,
13
+ DEFAULT_MAX_SAVED_CHATS,
14
+ )
9
15
  from yaicli.utils import str2bool
10
16
 
11
- DEFAULT_CONFIG_MAP = {
12
- # Core API settings
13
- "BASE_URL": {"value": "https://api.openai.com/v1", "env_key": "YAI_BASE_URL", "type": str},
14
- "API_KEY": {"value": "", "env_key": "YAI_API_KEY", "type": str},
15
- "MODEL": {"value": "gpt-4o", "env_key": "YAI_MODEL", "type": str},
16
- # System detection hints
17
- "SHELL_NAME": {"value": "auto", "env_key": "YAI_SHELL_NAME", "type": str},
18
- "OS_NAME": {"value": "auto", "env_key": "YAI_OS_NAME", "type": str},
19
- # API response parsing
20
- "COMPLETION_PATH": {"value": "chat/completions", "env_key": "YAI_COMPLETION_PATH", "type": str},
21
- "ANSWER_PATH": {"value": "choices[0].message.content", "env_key": "YAI_ANSWER_PATH", "type": str},
22
- # API call parameters
23
- "STREAM": {"value": "true", "env_key": "YAI_STREAM", "type": bool},
24
- "TEMPERATURE": {"value": "0.7", "env_key": "YAI_TEMPERATURE", "type": float},
25
- "TOP_P": {"value": "1.0", "env_key": "YAI_TOP_P", "type": float},
26
- "MAX_TOKENS": {"value": "1024", "env_key": "YAI_MAX_TOKENS", "type": int},
27
- # UI/UX settings
28
- "CODE_THEME": {"value": "monokai", "env_key": "YAI_CODE_THEME", "type": str},
29
- "MAX_HISTORY": {"value": "500", "env_key": "YAI_MAX_HISTORY", "type": int},
30
- "AUTO_SUGGEST": {"value": "true", "env_key": "YAI_AUTO_SUGGEST", "type": bool},
31
- }
32
-
33
- DEFAULT_CONFIG_INI = f"""[core]
34
- PROVIDER=openai
35
- BASE_URL={DEFAULT_CONFIG_MAP["BASE_URL"]["value"]}
36
- API_KEY={DEFAULT_CONFIG_MAP["API_KEY"]["value"]}
37
- MODEL={DEFAULT_CONFIG_MAP["MODEL"]["value"]}
38
-
39
- # auto detect shell and os (or specify manually, e.g., bash, zsh, powershell.exe)
40
- SHELL_NAME={DEFAULT_CONFIG_MAP["SHELL_NAME"]["value"]}
41
- OS_NAME={DEFAULT_CONFIG_MAP["OS_NAME"]["value"]}
42
-
43
- # API paths (usually no need to change for OpenAI compatible APIs)
44
- COMPLETION_PATH={DEFAULT_CONFIG_MAP["COMPLETION_PATH"]["value"]}
45
- ANSWER_PATH={DEFAULT_CONFIG_MAP["ANSWER_PATH"]["value"]}
46
-
47
- # true: streaming response, false: non-streaming
48
- STREAM={DEFAULT_CONFIG_MAP["STREAM"]["value"]}
49
-
50
- # LLM parameters
51
- TEMPERATURE={DEFAULT_CONFIG_MAP["TEMPERATURE"]["value"]}
52
- TOP_P={DEFAULT_CONFIG_MAP["TOP_P"]["value"]}
53
- MAX_TOKENS={DEFAULT_CONFIG_MAP["MAX_TOKENS"]["value"]}
54
-
55
- # UI/UX
56
- CODE_THEME={DEFAULT_CONFIG_MAP["CODE_THEME"]["value"]}
57
- MAX_HISTORY={DEFAULT_CONFIG_MAP["MAX_HISTORY"]["value"]}
58
- AUTO_SUGGEST={DEFAULT_CONFIG_MAP["AUTO_SUGGEST"]["value"]}
59
- """
60
-
61
- CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
62
-
63
17
 
64
18
  class CasePreservingConfigParser(configparser.RawConfigParser):
65
19
  """Case preserving config parser"""
@@ -95,9 +49,7 @@ class Config(dict):
95
49
  self.update(self._load_defaults())
96
50
 
97
51
  # Load from config file
98
- file_config = self._load_from_file()
99
- if file_config:
100
- self.update(file_config)
52
+ self._load_from_file()
101
53
 
102
54
  # Load from environment variables and apply type conversion
103
55
  self._load_from_env()
@@ -111,26 +63,44 @@ class Config(dict):
111
63
  """
112
64
  return {k: v["value"] for k, v in DEFAULT_CONFIG_MAP.items()}
113
65
 
114
- def _load_from_file(self) -> dict[str, str]:
66
+ def _ensure_version_updated_config_keys(self):
67
+ """Ensure configuration keys added in version updates exist in the config file.
68
+ Appends missing keys to the config file if they don't exist.
69
+ """
70
+ with open(CONFIG_PATH, "r+", encoding="utf-8") as f:
71
+ config_content = f.read()
72
+ if "CHAT_HISTORY_DIR" not in config_content.strip(): # Check for empty lines
73
+ f.write(f"\nCHAT_HISTORY_DIR={DEFAULT_CHAT_HISTORY_DIR}\n")
74
+ if "MAX_SAVED_CHATS" not in config_content.strip(): # Check for empty lines
75
+ f.write(f"\nMAX_SAVED_CHATS={DEFAULT_MAX_SAVED_CHATS}\n")
76
+
77
+ def _load_from_file(self) -> None:
115
78
  """Load configuration from the config file.
116
79
 
117
80
  Creates default config file if it doesn't exist.
118
-
119
- Returns:
120
- Dictionary with configuration values from file, or empty dict if no valid values
121
81
  """
122
82
  if not CONFIG_PATH.exists():
123
83
  self.console.print("Creating default configuration file.", style="bold yellow")
124
84
  CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
125
85
  with open(CONFIG_PATH, "w", encoding="utf-8") as f:
126
86
  f.write(DEFAULT_CONFIG_INI)
127
- return {}
87
+ return
128
88
 
129
89
  config_parser = CasePreservingConfigParser()
130
90
  config_parser.read(CONFIG_PATH, encoding="utf-8")
131
- if "core" in config_parser:
132
- return {k: v for k, v in config_parser["core"].items() if k in DEFAULT_CONFIG_MAP and v.strip()}
133
- return {}
91
+
92
+ # Check if "core" section exists in the config file
93
+ if "core" not in config_parser or not config_parser["core"]:
94
+ return
95
+
96
+ for k, v in {"SHELL_NAME": "Unknown Shell", "OS_NAME": "Unknown OS"}.items():
97
+ if not config_parser["core"].get(k, "").strip():
98
+ config_parser["core"][k] = v
99
+
100
+ self.update(config_parser["core"])
101
+
102
+ # Check if keys added in version updates are missing and add them
103
+ self._ensure_version_updated_config_keys()
134
104
 
135
105
  def _load_from_env(self) -> None:
136
106
  """Load configuration from environment variables.
yaicli/const.py CHANGED
@@ -1,15 +1,21 @@
1
1
  from enum import StrEnum
2
+ from pathlib import Path
3
+ from tempfile import gettempdir
2
4
 
3
5
  CMD_CLEAR = "/clear"
4
6
  CMD_EXIT = "/exit"
5
7
  CMD_HISTORY = "/his"
6
8
  CMD_MODE = "/mode"
9
+ CMD_SAVE_CHAT = "/save"
10
+ CMD_LOAD_CHAT = "/load"
11
+ CMD_LIST_CHATS = "/list"
12
+ CMD_DELETE_CHAT = "/del"
7
13
 
8
14
  EXEC_MODE = "exec"
9
15
  CHAT_MODE = "chat"
10
16
  TEMP_MODE = "temp"
11
17
 
12
- DEFAULT_CONFIG_PATH = "~/.config/yaicli/config.ini"
18
+ CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
13
19
  DEFAULT_CODE_THEME = "monokai"
14
20
  DEFAULT_COMPLETION_PATH = "chat/completions"
15
21
  DEFAULT_ANSWER_PATH = "choices[0].message.content"
@@ -26,6 +32,8 @@ DEFAULT_MAX_HISTORY: int = 500
26
32
  DEFAULT_AUTO_SUGGEST = "true"
27
33
  DEFAULT_TIMEOUT: int = 60
28
34
  DEFAULT_INTERACTIVE_ROUND: int = 25
35
+ DEFAULT_CHAT_HISTORY_DIR = Path(gettempdir()) / "yaicli/chats"
36
+ DEFAULT_MAX_SAVED_CHATS = 20
29
37
 
30
38
 
31
39
  class EventTypeEnum(StrEnum):
@@ -66,7 +74,7 @@ DEFAULT_CONFIG_MAP = {
66
74
  # System detection hints
67
75
  "SHELL_NAME": {"value": DEFAULT_SHELL_NAME, "env_key": "YAI_SHELL_NAME", "type": str},
68
76
  "OS_NAME": {"value": DEFAULT_OS_NAME, "env_key": "YAI_OS_NAME", "type": str},
69
- # API response parsing
77
+ # API paths (usually no need to change for OpenAI compatible APIs)
70
78
  "COMPLETION_PATH": {"value": DEFAULT_COMPLETION_PATH, "env_key": "YAI_COMPLETION_PATH", "type": str},
71
79
  "ANSWER_PATH": {"value": DEFAULT_ANSWER_PATH, "env_key": "YAI_ANSWER_PATH", "type": str},
72
80
  # API call parameters
@@ -84,6 +92,9 @@ DEFAULT_CONFIG_MAP = {
84
92
  "CODE_THEME": {"value": DEFAULT_CODE_THEME, "env_key": "YAI_CODE_THEME", "type": str},
85
93
  "MAX_HISTORY": {"value": DEFAULT_MAX_HISTORY, "env_key": "YAI_MAX_HISTORY", "type": int},
86
94
  "AUTO_SUGGEST": {"value": DEFAULT_AUTO_SUGGEST, "env_key": "YAI_AUTO_SUGGEST", "type": bool},
95
+ # Chat history settings
96
+ "CHAT_HISTORY_DIR": {"value": DEFAULT_CHAT_HISTORY_DIR, "env_key": "YAI_CHAT_HISTORY_DIR", "type": str},
97
+ "MAX_SAVED_CHATS": {"value": DEFAULT_MAX_SAVED_CHATS, "env_key": "YAI_MAX_SAVED_CHATS", "type": int},
87
98
  }
88
99
 
89
100
  DEFAULT_CONFIG_INI = f"""[core]
@@ -114,6 +125,11 @@ INTERACTIVE_ROUND={DEFAULT_CONFIG_MAP["INTERACTIVE_ROUND"]["value"]}
114
125
 
115
126
  # UI/UX
116
127
  CODE_THEME={DEFAULT_CONFIG_MAP["CODE_THEME"]["value"]}
117
- MAX_HISTORY={DEFAULT_CONFIG_MAP["MAX_HISTORY"]["value"]} # Max entries kept in history file
128
+ # Max entries kept in history file
129
+ MAX_HISTORY={DEFAULT_CONFIG_MAP["MAX_HISTORY"]["value"]}
118
130
  AUTO_SUGGEST={DEFAULT_CONFIG_MAP["AUTO_SUGGEST"]["value"]}
131
+
132
+ # Chat history settings
133
+ CHAT_HISTORY_DIR={DEFAULT_CONFIG_MAP["CHAT_HISTORY_DIR"]["value"]}
134
+ MAX_SAVED_CHATS={DEFAULT_CONFIG_MAP["MAX_SAVED_CHATS"]["value"]}
119
135
  """
yaicli/entry.py CHANGED
@@ -22,7 +22,7 @@ def main(
22
22
  Optional[str], typer.Argument(help="The prompt to send to the LLM. Reads from stdin if available.")
23
23
  ] = None,
24
24
  chat: Annotated[
25
- bool, typer.Option("--chat", "-c", help="Start in interactive chat mode.", rich_help_panel="Mode Options")
25
+ bool, typer.Option("--chat", "-c", help="Start in interactive chat mode.", rich_help_panel="Chat Options")
26
26
  ] = False,
27
27
  shell: Annotated[
28
28
  bool,
@@ -30,7 +30,16 @@ def main(
30
30
  "--shell",
31
31
  "-s",
32
32
  help="Generate and optionally execute a shell command (non-interactive).",
33
- rich_help_panel="Mode Options",
33
+ rich_help_panel="Shell Options",
34
+ ),
35
+ ] = False,
36
+ list_chats: Annotated[
37
+ bool,
38
+ typer.Option(
39
+ "--list-chats",
40
+ "-lc",
41
+ help="List saved chat sessions.",
42
+ rich_help_panel="Chat Options",
34
43
  ),
35
44
  ] = False,
36
45
  verbose: Annotated[
@@ -65,20 +74,27 @@ def main(
65
74
  final_prompt = f"{stdin_content}\n\n{final_prompt}"
66
75
  else:
67
76
  final_prompt = stdin_content
77
+ # prompt_toolkit will raise EOFError if stdin is redirected
78
+ # Set chat to False to avoid starting interactive mode
79
+ if chat:
80
+ print("Warning: --chat is ignored when stdin was redirected.")
81
+ chat = False
68
82
 
69
83
  # Basic validation for conflicting options or missing prompt
70
- if not final_prompt and not chat:
71
- # If no prompt and not starting chat, show help
84
+ if not final_prompt and not chat and not list_chats:
85
+ # If no prompt, not starting chat, and not listing chats, show help
72
86
  typer.echo(ctx.get_help())
73
87
  raise typer.Exit()
74
- if chat and final_prompt:
75
- # Warn if both chat mode and a prompt are given (prompt will be ignored)
76
- # Or, could use the prompt as the first message in chat mode
77
- print("Warning: Starting in chat mode. Initial prompt argument will be ignored.")
78
88
 
79
89
  try:
80
90
  # Instantiate the main CLI class
81
91
  cli_instance = CLI(verbose=verbose)
92
+
93
+ # Handle list_chats option
94
+ if list_chats:
95
+ cli_instance._list_chats()
96
+ return
97
+
82
98
  # Run the appropriate mode
83
99
  cli_instance.run(chat=chat, shell=shell, prompt=final_prompt)
84
100
  except Exception as e: