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.
- pyproject.toml +1 -1
- yaicli/chat_manager.py +263 -0
- yaicli/cli.py +192 -10
- yaicli/config.py +34 -64
- yaicli/const.py +19 -3
- yaicli/entry.py +24 -8
- {yaicli-0.1.0.dist-info → yaicli-0.2.0.dist-info}/METADATA +214 -99
- yaicli-0.2.0.dist-info/RECORD +16 -0
- yaicli-0.1.0.dist-info/RECORD +0 -15
- {yaicli-0.1.0.dist-info → yaicli-0.2.0.dist-info}/WHEEL +0 -0
- {yaicli-0.1.0.dist-info → yaicli-0.2.0.dist-info}/entry_points.txt +0 -0
- {yaicli-0.1.0.dist-info → yaicli-0.2.0.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
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
|
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
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
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
|
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
|
-
|
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="
|
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="
|
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
|
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:
|