yaicli 0.2.0__py3-none-any.whl → 0.3.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/api.py +14 -26
- yaicli/chat_manager.py +46 -19
- yaicli/cli.py +101 -68
- yaicli/config.py +17 -1
- yaicli/console.py +66 -0
- yaicli/const.py +51 -6
- yaicli/entry.py +173 -50
- yaicli/exceptions.py +46 -0
- yaicli/printer.py +113 -57
- yaicli/render.py +19 -0
- yaicli/roles.py +248 -0
- yaicli/utils.py +21 -2
- {yaicli-0.2.0.dist-info → yaicli-0.3.0.dist-info}/METADATA +105 -89
- yaicli-0.3.0.dist-info/RECORD +20 -0
- yaicli-0.2.0.dist-info/RECORD +0 -16
- {yaicli-0.2.0.dist-info → yaicli-0.3.0.dist-info}/WHEEL +0 -0
- {yaicli-0.2.0.dist-info → yaicli-0.3.0.dist-info}/entry_points.txt +0 -0
- {yaicli-0.2.0.dist-info → yaicli-0.3.0.dist-info}/licenses/LICENSE +0 -0
yaicli/console.py
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
from typing import Any, Optional, Union
|
2
|
+
|
3
|
+
from rich.console import Console, JustifyMethod, OverflowMethod
|
4
|
+
from rich.style import Style
|
5
|
+
|
6
|
+
from yaicli.config import cfg
|
7
|
+
from yaicli.const import DEFAULT_JUSTIFY
|
8
|
+
|
9
|
+
_console = None
|
10
|
+
|
11
|
+
|
12
|
+
class YaiConsole(Console):
|
13
|
+
"""Custom Console class that defaults to the configured justify value."""
|
14
|
+
|
15
|
+
def __init__(self, *args, **kwargs):
|
16
|
+
super().__init__(*args, **kwargs)
|
17
|
+
self._default_justify: JustifyMethod = DEFAULT_JUSTIFY
|
18
|
+
if "JUSTIFY" in cfg:
|
19
|
+
self._default_justify = cfg["JUSTIFY"]
|
20
|
+
|
21
|
+
def print(
|
22
|
+
self,
|
23
|
+
*objects: Any,
|
24
|
+
sep: str = " ",
|
25
|
+
end: str = "\n",
|
26
|
+
style: Optional[Union[str, Style]] = None,
|
27
|
+
justify: Optional[JustifyMethod] = None,
|
28
|
+
overflow: Optional[OverflowMethod] = None,
|
29
|
+
no_wrap: Optional[bool] = None,
|
30
|
+
emoji: Optional[bool] = None,
|
31
|
+
markup: Optional[bool] = None,
|
32
|
+
highlight: Optional[bool] = None,
|
33
|
+
width: Optional[int] = None,
|
34
|
+
height: Optional[int] = None,
|
35
|
+
crop: bool = True,
|
36
|
+
soft_wrap: Optional[bool] = None,
|
37
|
+
new_line_start: bool = False,
|
38
|
+
):
|
39
|
+
"""Override the print method to default to the configured justify value."""
|
40
|
+
if justify is None:
|
41
|
+
justify = self._default_justify
|
42
|
+
return super().print(
|
43
|
+
*objects,
|
44
|
+
sep=sep,
|
45
|
+
end=end,
|
46
|
+
style=style,
|
47
|
+
justify=justify,
|
48
|
+
overflow=overflow,
|
49
|
+
no_wrap=no_wrap,
|
50
|
+
emoji=emoji,
|
51
|
+
markup=markup,
|
52
|
+
highlight=highlight,
|
53
|
+
width=width,
|
54
|
+
height=height,
|
55
|
+
crop=crop,
|
56
|
+
soft_wrap=soft_wrap,
|
57
|
+
new_line_start=new_line_start,
|
58
|
+
)
|
59
|
+
|
60
|
+
|
61
|
+
def get_console() -> YaiConsole:
|
62
|
+
"""Use a singleton pattern to ensure only one instance of Console is created."""
|
63
|
+
global _console
|
64
|
+
if _console is None:
|
65
|
+
_console = YaiConsole()
|
66
|
+
return _console
|
yaicli/const.py
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
from enum import StrEnum
|
2
2
|
from pathlib import Path
|
3
3
|
from tempfile import gettempdir
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
from rich.console import JustifyMethod
|
7
|
+
|
8
|
+
|
9
|
+
class JustifyEnum(StrEnum):
|
10
|
+
DEFAULT = "default"
|
11
|
+
LEFT = "left"
|
12
|
+
CENTER = "center"
|
13
|
+
RIGHT = "right"
|
14
|
+
FULL = "full"
|
15
|
+
|
4
16
|
|
5
17
|
CMD_CLEAR = "/clear"
|
6
18
|
CMD_EXIT = "/exit"
|
@@ -14,8 +26,12 @@ CMD_DELETE_CHAT = "/del"
|
|
14
26
|
EXEC_MODE = "exec"
|
15
27
|
CHAT_MODE = "chat"
|
16
28
|
TEMP_MODE = "temp"
|
29
|
+
CODE_MODE = "code"
|
17
30
|
|
18
31
|
CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
|
32
|
+
ROLES_DIR = CONFIG_PATH.parent / "roles"
|
33
|
+
|
34
|
+
# Default configuration values
|
19
35
|
DEFAULT_CODE_THEME = "monokai"
|
20
36
|
DEFAULT_COMPLETION_PATH = "chat/completions"
|
21
37
|
DEFAULT_ANSWER_PATH = "choices[0].message.content"
|
@@ -30,10 +46,12 @@ DEFAULT_TOP_P: float = 1.0
|
|
30
46
|
DEFAULT_MAX_TOKENS: int = 1024
|
31
47
|
DEFAULT_MAX_HISTORY: int = 500
|
32
48
|
DEFAULT_AUTO_SUGGEST = "true"
|
49
|
+
DEFAULT_SHOW_REASONING = "true"
|
33
50
|
DEFAULT_TIMEOUT: int = 60
|
34
51
|
DEFAULT_INTERACTIVE_ROUND: int = 25
|
35
52
|
DEFAULT_CHAT_HISTORY_DIR = Path(gettempdir()) / "yaicli/chats"
|
36
53
|
DEFAULT_MAX_SAVED_CHATS = 20
|
54
|
+
DEFAULT_JUSTIFY: JustifyMethod = "default"
|
37
55
|
|
38
56
|
|
39
57
|
class EventTypeEnum(StrEnum):
|
@@ -46,20 +64,41 @@ class EventTypeEnum(StrEnum):
|
|
46
64
|
FINISH = "finish"
|
47
65
|
|
48
66
|
|
49
|
-
SHELL_PROMPT = """Your are a Shell Command Generator.
|
67
|
+
SHELL_PROMPT = """Your are a Shell Command Generator named YAICLI.
|
50
68
|
Generate a command EXCLUSIVELY for {_os} OS with {_shell} shell.
|
51
69
|
If details are missing, offer the most logical solution.
|
52
70
|
Ensure the output is a valid shell command.
|
53
71
|
Combine multiple steps with `&&` when possible.
|
54
72
|
Supply plain text only, avoiding Markdown formatting."""
|
55
73
|
|
56
|
-
DEFAULT_PROMPT =
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
74
|
+
DEFAULT_PROMPT = """
|
75
|
+
You are YAICLI, a system management and programing assistant,
|
76
|
+
You are managing {_os} operating system with {_shell} shell.
|
77
|
+
Your responses should be concise and use Markdown format (but dont't use ```markdown),
|
78
|
+
unless the user explicitly requests more details.
|
79
|
+
"""
|
80
|
+
|
81
|
+
CODER_PROMPT = (
|
82
|
+
"You are YAICLI, a code assistant. "
|
83
|
+
"You are helping with programming tasks. "
|
84
|
+
"Your responses must ONLY contain code, with NO explanation, NO markdown formatting, and NO preamble. "
|
85
|
+
"If user does not specify the language, provide Python code. "
|
86
|
+
"Do not wrap code in markdown code blocks (```) or language indicators."
|
61
87
|
)
|
62
88
|
|
89
|
+
|
90
|
+
class DefaultRoleNames(StrEnum):
|
91
|
+
SHELL = "Shell Command Generator"
|
92
|
+
DEFAULT = "DEFAULT"
|
93
|
+
CODER = "Code Assistant"
|
94
|
+
|
95
|
+
|
96
|
+
DEFAULT_ROLES: dict[str, dict[str, Any]] = {
|
97
|
+
DefaultRoleNames.SHELL: {"name": DefaultRoleNames.SHELL, "prompt": SHELL_PROMPT},
|
98
|
+
DefaultRoleNames.DEFAULT: {"name": DefaultRoleNames.DEFAULT, "prompt": DEFAULT_PROMPT},
|
99
|
+
DefaultRoleNames.CODER: {"name": DefaultRoleNames.CODER, "prompt": CODER_PROMPT},
|
100
|
+
}
|
101
|
+
|
63
102
|
# DEFAULT_CONFIG_MAP is a dictionary of the configuration options.
|
64
103
|
# The key is the name of the configuration option.
|
65
104
|
# The value is a dictionary with the following keys:
|
@@ -92,6 +131,8 @@ DEFAULT_CONFIG_MAP = {
|
|
92
131
|
"CODE_THEME": {"value": DEFAULT_CODE_THEME, "env_key": "YAI_CODE_THEME", "type": str},
|
93
132
|
"MAX_HISTORY": {"value": DEFAULT_MAX_HISTORY, "env_key": "YAI_MAX_HISTORY", "type": int},
|
94
133
|
"AUTO_SUGGEST": {"value": DEFAULT_AUTO_SUGGEST, "env_key": "YAI_AUTO_SUGGEST", "type": bool},
|
134
|
+
"SHOW_REASONING": {"value": DEFAULT_SHOW_REASONING, "env_key": "YAI_SHOW_REASONING", "type": bool},
|
135
|
+
"JUSTIFY": {"value": DEFAULT_JUSTIFY, "env_key": "YAI_JUSTIFY", "type": str},
|
95
136
|
# Chat history settings
|
96
137
|
"CHAT_HISTORY_DIR": {"value": DEFAULT_CHAT_HISTORY_DIR, "env_key": "YAI_CHAT_HISTORY_DIR", "type": str},
|
97
138
|
"MAX_SAVED_CHATS": {"value": DEFAULT_MAX_SAVED_CHATS, "env_key": "YAI_MAX_SAVED_CHATS", "type": int},
|
@@ -128,6 +169,10 @@ CODE_THEME={DEFAULT_CONFIG_MAP["CODE_THEME"]["value"]}
|
|
128
169
|
# Max entries kept in history file
|
129
170
|
MAX_HISTORY={DEFAULT_CONFIG_MAP["MAX_HISTORY"]["value"]}
|
130
171
|
AUTO_SUGGEST={DEFAULT_CONFIG_MAP["AUTO_SUGGEST"]["value"]}
|
172
|
+
# Print reasoning content or not
|
173
|
+
SHOW_REASONING={DEFAULT_CONFIG_MAP["SHOW_REASONING"]["value"]}
|
174
|
+
# Text alignment (default, left, center, right, full)
|
175
|
+
JUSTIFY={DEFAULT_CONFIG_MAP["JUSTIFY"]["value"]}
|
131
176
|
|
132
177
|
# Chat history settings
|
133
178
|
CHAT_HISTORY_DIR={DEFAULT_CONFIG_MAP["CHAT_HISTORY_DIR"]["value"]}
|
yaicli/entry.py
CHANGED
@@ -1,64 +1,179 @@
|
|
1
1
|
import sys
|
2
|
-
from typing import Annotated, Optional
|
2
|
+
from typing import Annotated, Any, Optional
|
3
3
|
|
4
4
|
import typer
|
5
5
|
|
6
|
+
from yaicli.chat_manager import FileChatManager
|
6
7
|
from yaicli.cli import CLI
|
7
|
-
from yaicli.
|
8
|
+
from yaicli.config import cfg
|
9
|
+
from yaicli.const import DEFAULT_CONFIG_INI, DefaultRoleNames, JustifyEnum
|
10
|
+
from yaicli.roles import RoleManager
|
8
11
|
|
9
12
|
app = typer.Typer(
|
10
13
|
name="yaicli",
|
11
14
|
help="YAICLI - Yet Another AI CLI Interface.",
|
12
15
|
context_settings={"help_option_names": ["-h", "--help"]},
|
13
16
|
pretty_exceptions_enable=False, # Let the CLI handle errors gracefully
|
14
|
-
|
17
|
+
rich_markup_mode="rich", # Render rich text in help messages
|
15
18
|
)
|
16
19
|
|
17
20
|
|
21
|
+
def override_config(
|
22
|
+
ctx: typer.Context, # noqa: F841
|
23
|
+
param: typer.CallbackParam,
|
24
|
+
value: Any,
|
25
|
+
):
|
26
|
+
"""Override config with input value if value not equal to option default."""
|
27
|
+
if value != param.default and isinstance(param.name, str):
|
28
|
+
cfg[param.name.upper()] = value
|
29
|
+
return value
|
30
|
+
|
31
|
+
|
18
32
|
@app.command()
|
19
33
|
def main(
|
20
34
|
ctx: typer.Context,
|
21
35
|
prompt: Annotated[
|
22
36
|
Optional[str], typer.Argument(help="The prompt to send to the LLM. Reads from stdin if available.")
|
23
37
|
] = None,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
38
|
+
# ------------------- LLM Options -------------------
|
39
|
+
model: str = typer.Option( # noqa: F841
|
40
|
+
"",
|
41
|
+
"--model",
|
42
|
+
"-M",
|
43
|
+
help="Specify the model to use.",
|
44
|
+
rich_help_panel="LLM Options",
|
45
|
+
callback=override_config,
|
46
|
+
),
|
47
|
+
temperature: float = typer.Option( # noqa: F841
|
48
|
+
cfg["TEMPERATURE"],
|
49
|
+
"--temperature",
|
50
|
+
"-T",
|
51
|
+
help="Specify the temperature to use.",
|
52
|
+
rich_help_panel="LLM Options",
|
53
|
+
min=0.0,
|
54
|
+
max=2.0,
|
55
|
+
callback=override_config,
|
56
|
+
),
|
57
|
+
top_p: float = typer.Option( # noqa: F841
|
58
|
+
cfg["TOP_P"],
|
59
|
+
"--top-p",
|
60
|
+
"-P",
|
61
|
+
help="Specify the top-p to use.",
|
62
|
+
rich_help_panel="LLM Options",
|
63
|
+
min=0.0,
|
64
|
+
max=1.0,
|
65
|
+
callback=override_config,
|
66
|
+
),
|
67
|
+
max_tokens: int = typer.Option( # noqa: F841
|
68
|
+
cfg["MAX_TOKENS"],
|
69
|
+
"--max-tokens",
|
70
|
+
"-M",
|
71
|
+
help="Specify the max tokens to use.",
|
72
|
+
rich_help_panel="LLM Options",
|
73
|
+
min=1,
|
74
|
+
callback=override_config,
|
75
|
+
),
|
76
|
+
# ------------------- Role Options -------------------
|
77
|
+
role: str = typer.Option(
|
78
|
+
DefaultRoleNames.DEFAULT,
|
79
|
+
"--role",
|
80
|
+
"-r",
|
81
|
+
help="Specify the assistant role to use.",
|
82
|
+
rich_help_panel="Role Options",
|
83
|
+
callback=RoleManager.check_id_ok,
|
84
|
+
),
|
85
|
+
create_role: str = typer.Option(
|
86
|
+
"",
|
87
|
+
"--create-role",
|
88
|
+
help="Create a new role with the specified name.",
|
89
|
+
rich_help_panel="Role Options",
|
90
|
+
callback=RoleManager.create_role_option,
|
91
|
+
),
|
92
|
+
delete_role: str = typer.Option( # noqa: F841
|
93
|
+
"",
|
94
|
+
"--delete-role",
|
95
|
+
help="Delete a role with the specified name.",
|
96
|
+
rich_help_panel="Role Options",
|
97
|
+
callback=RoleManager.delete_role_option,
|
98
|
+
),
|
99
|
+
list_roles: bool = typer.Option(
|
100
|
+
False,
|
101
|
+
"--list-roles",
|
102
|
+
help="List all available roles.",
|
103
|
+
rich_help_panel="Role Options",
|
104
|
+
callback=RoleManager.print_list_option,
|
105
|
+
),
|
106
|
+
show_role: str = typer.Option( # noqa: F841
|
107
|
+
"",
|
108
|
+
"--show-role",
|
109
|
+
help="Show the role with the specified name.",
|
110
|
+
rich_help_panel="Role Options",
|
111
|
+
callback=RoleManager.show_role_option,
|
112
|
+
),
|
113
|
+
# ------------------- Chat Options -------------------
|
114
|
+
chat: bool = typer.Option(
|
115
|
+
False,
|
116
|
+
"--chat",
|
117
|
+
"-c",
|
118
|
+
help="Start in interactive chat mode.",
|
119
|
+
rich_help_panel="Chat Options",
|
120
|
+
),
|
121
|
+
# ------------------- Shell Options -------------------
|
122
|
+
shell: bool = typer.Option(
|
123
|
+
False,
|
124
|
+
"--shell",
|
125
|
+
"-s",
|
126
|
+
help="Generate and optionally execute a shell command (non-interactive).",
|
127
|
+
rich_help_panel="Shell Options",
|
128
|
+
),
|
129
|
+
# ------------------- Code Options -------------------
|
130
|
+
code: bool = typer.Option(
|
131
|
+
False,
|
132
|
+
"--code",
|
133
|
+
help="Generate and optionally execute a code block (non-interactive).",
|
134
|
+
rich_help_panel="Code Options",
|
135
|
+
),
|
136
|
+
# ------------------- Chat Options -------------------
|
137
|
+
list_chats: bool = typer.Option(
|
138
|
+
False,
|
139
|
+
"--list-chats",
|
140
|
+
help="List saved chat sessions.",
|
141
|
+
rich_help_panel="Chat Options",
|
142
|
+
callback=FileChatManager.print_list_option,
|
143
|
+
),
|
144
|
+
# ------------------- Other Options -------------------
|
145
|
+
verbose: bool = typer.Option(
|
146
|
+
False,
|
147
|
+
"--verbose",
|
148
|
+
"-V",
|
149
|
+
help="Show verbose output (e.g., loaded config).",
|
150
|
+
rich_help_panel="Other Options",
|
151
|
+
),
|
152
|
+
template: bool = typer.Option(
|
153
|
+
False,
|
154
|
+
"--template",
|
155
|
+
help="Show the default config file template and exit.",
|
156
|
+
rich_help_panel="Other Options",
|
157
|
+
),
|
158
|
+
show_reasoning: bool = typer.Option( # noqa: F841
|
159
|
+
cfg["SHOW_REASONING"],
|
160
|
+
help=f"Show reasoning content from the LLM. [dim](default: {cfg['SHOW_REASONING']})[/dim]",
|
161
|
+
rich_help_panel="Other Options",
|
162
|
+
show_default=False,
|
163
|
+
callback=override_config,
|
164
|
+
),
|
165
|
+
justify: JustifyEnum = typer.Option( # noqa: F841
|
166
|
+
cfg["JUSTIFY"],
|
167
|
+
"--justify",
|
168
|
+
"-j",
|
169
|
+
help="Specify the justify to use.",
|
170
|
+
rich_help_panel="Other Options",
|
171
|
+
callback=override_config,
|
172
|
+
),
|
173
|
+
):
|
58
174
|
"""YAICLI: Your AI assistant in the command line.
|
59
175
|
|
60
|
-
Call with a PROMPT to get a direct answer, use --shell to execute as command,
|
61
|
-
or use --chat for an interactive session.
|
176
|
+
Call with a PROMPT to get a direct answer, use --shell to execute as command, or use --chat for an interactive session.
|
62
177
|
"""
|
63
178
|
if template:
|
64
179
|
print(DEFAULT_CONFIG_INI)
|
@@ -75,28 +190,36 @@ def main(
|
|
75
190
|
else:
|
76
191
|
final_prompt = stdin_content
|
77
192
|
# prompt_toolkit will raise EOFError if stdin is redirected
|
78
|
-
# Set chat to False to
|
193
|
+
# Set chat to False to prevent starting interactive mode.
|
79
194
|
if chat:
|
80
195
|
print("Warning: --chat is ignored when stdin was redirected.")
|
81
196
|
chat = False
|
82
197
|
|
83
198
|
# Basic validation for conflicting options or missing prompt
|
84
|
-
if not final_prompt
|
85
|
-
# If no prompt, not starting chat, and not listing chats, show help
|
199
|
+
if not any([final_prompt, chat, list_chats, list_roles, create_role]):
|
200
|
+
# If no prompt, not starting chat, and not listing chats or roles, show help
|
86
201
|
typer.echo(ctx.get_help())
|
87
202
|
raise typer.Exit()
|
88
203
|
|
89
|
-
|
90
|
-
|
91
|
-
|
204
|
+
# Use build-in role for --shell or --code mode
|
205
|
+
if role and role != DefaultRoleNames.DEFAULT and (shell or code):
|
206
|
+
print("Warning: --role is ignored when --shell or --code is used.")
|
207
|
+
role = DefaultRoleNames.DEFAULT
|
92
208
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
209
|
+
if code:
|
210
|
+
role = DefaultRoleNames.CODER
|
211
|
+
|
212
|
+
try:
|
213
|
+
# Instantiate the main CLI class with the specified role
|
214
|
+
cli_instance = CLI(verbose=verbose, role=role)
|
97
215
|
|
98
216
|
# Run the appropriate mode
|
99
|
-
cli_instance.run(
|
217
|
+
cli_instance.run(
|
218
|
+
chat=chat,
|
219
|
+
shell=shell,
|
220
|
+
input=final_prompt,
|
221
|
+
role=role,
|
222
|
+
)
|
100
223
|
except Exception as e:
|
101
224
|
# Catch potential errors during CLI initialization or run
|
102
225
|
print(f"An error occurred: {e}")
|
yaicli/exceptions.py
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class YAICLIException(Exception):
|
2
|
+
"""Base exception for YAICLI"""
|
3
|
+
|
4
|
+
def __init__(self, message: str):
|
5
|
+
self.message = message
|
6
|
+
super().__init__(self.message)
|
7
|
+
|
8
|
+
|
9
|
+
###################################
|
10
|
+
# Role exceptions
|
11
|
+
class RoleNotFoundError(YAICLIException):
|
12
|
+
"""Exception raised when a role is not found"""
|
13
|
+
|
14
|
+
pass
|
15
|
+
|
16
|
+
|
17
|
+
class RoleAlreadyExistsError(YAICLIException):
|
18
|
+
"""Exception raised when a role already exists"""
|
19
|
+
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class RoleCreationError(YAICLIException):
|
24
|
+
"""Exception raised when a role creation fails"""
|
25
|
+
|
26
|
+
pass
|
27
|
+
|
28
|
+
|
29
|
+
###################################
|
30
|
+
# Chat exceptions
|
31
|
+
class ChatNotFoundError(YAICLIException):
|
32
|
+
"""Exception raised when a chat is not found"""
|
33
|
+
|
34
|
+
pass
|
35
|
+
|
36
|
+
|
37
|
+
class ChatSaveError(YAICLIException):
|
38
|
+
"""Exception raised when a chat save fails"""
|
39
|
+
|
40
|
+
pass
|
41
|
+
|
42
|
+
|
43
|
+
class ChatDeleteError(YAICLIException):
|
44
|
+
"""Exception raised when a chat delete fails"""
|
45
|
+
|
46
|
+
pass
|