yaicli 0.4.0__py3-none-any.whl → 0.5.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/entry.py CHANGED
@@ -3,15 +3,15 @@ from typing import Annotated, Any, Optional
3
3
 
4
4
  import typer
5
5
 
6
- from yaicli.chat_manager import FileChatManager
7
- from yaicli.cli import CLI
8
- from yaicli.config import cfg
9
- from yaicli.const import DEFAULT_CONFIG_INI, DefaultRoleNames, JustifyEnum
10
- from yaicli.roles import RoleManager
6
+ from .chat import FileChatManager
7
+ from .config import cfg
8
+ from .const import DEFAULT_CONFIG_INI, DefaultRoleNames, JustifyEnum
9
+ from .functions import install_functions, print_functions
10
+ from .role import RoleManager
11
11
 
12
12
  app = typer.Typer(
13
13
  name="yaicli",
14
- help="YAICLI - Yet Another AI CLI Interface.",
14
+ help="YAICLI - Your AI assistant in the command line.",
15
15
  context_settings={"help_option_names": ["-h", "--help"]},
16
16
  pretty_exceptions_enable=False, # Let the CLI handle errors gracefully
17
17
  rich_markup_mode="rich", # Render rich text in help messages
@@ -73,6 +73,14 @@ def main(
73
73
  min=1,
74
74
  callback=override_config,
75
75
  ),
76
+ stream: bool = typer.Option( # noqa: F841
77
+ cfg["STREAM"],
78
+ "--stream/--no-stream",
79
+ help=f"Specify whether to stream the response. [dim](default: {'stream' if cfg['STREAM'] else 'no-stream'})[/dim]",
80
+ rich_help_panel="LLM Options",
81
+ show_default=False,
82
+ callback=override_config,
83
+ ),
76
84
  # ------------------- Role Options -------------------
77
85
  role: str = typer.Option(
78
86
  DefaultRoleNames.DEFAULT,
@@ -118,7 +126,7 @@ def main(
118
126
  help="Start in interactive chat mode.",
119
127
  rich_help_panel="Chat Options",
120
128
  ),
121
- # ------------------- Shell Options -------------------
129
+ # # ------------------- Shell Options -------------------
122
130
  shell: bool = typer.Option(
123
131
  False,
124
132
  "--shell",
@@ -126,7 +134,7 @@ def main(
126
134
  help="Generate and optionally execute a shell command (non-interactive).",
127
135
  rich_help_panel="Shell Options",
128
136
  ),
129
- # ------------------- Code Options -------------------
137
+ # # ------------------- Code Options -------------------
130
138
  code: bool = typer.Option(
131
139
  False,
132
140
  "--code",
@@ -157,7 +165,8 @@ def main(
157
165
  ),
158
166
  show_reasoning: bool = typer.Option( # noqa: F841
159
167
  cfg["SHOW_REASONING"],
160
- help=f"Show reasoning content from the LLM. [dim](default: {cfg['SHOW_REASONING']})[/dim]",
168
+ "--show-reasoning/--hide-reasoning",
169
+ help=f"Show reasoning content from the LLM. [dim](default: {'show' if cfg['SHOW_REASONING'] else 'hide'})[/dim]",
161
170
  rich_help_panel="Other Options",
162
171
  show_default=False,
163
172
  callback=override_config,
@@ -170,6 +179,37 @@ def main(
170
179
  rich_help_panel="Other Options",
171
180
  callback=override_config,
172
181
  ),
182
+ # ------------------- Function Options -------------------
183
+ install_functions: bool = typer.Option( # noqa: F841
184
+ False,
185
+ "--install-functions",
186
+ help="Install default functions.",
187
+ rich_help_panel="Function Options",
188
+ callback=install_functions,
189
+ ),
190
+ list_functions: bool = typer.Option( # noqa: F841
191
+ False,
192
+ "--list-functions",
193
+ help="List all available functions.",
194
+ rich_help_panel="Function Options",
195
+ callback=print_functions,
196
+ ),
197
+ enable_functions: bool = typer.Option( # noqa: F841
198
+ cfg["ENABLE_FUNCTIONS"],
199
+ "--enable-functions/--disable-functions",
200
+ help=f"Enable/disable function calling in API requests [dim](default: {'enabled' if cfg['ENABLE_FUNCTIONS'] else 'disabled'})[/dim]",
201
+ rich_help_panel="Function Options",
202
+ show_default=False,
203
+ callback=override_config,
204
+ ),
205
+ show_function_output: bool = typer.Option( # noqa: F841
206
+ cfg["SHOW_FUNCTION_OUTPUT"],
207
+ "--show-function-output/--hide-function-output",
208
+ help=f"Show the output of functions [dim](default: {'show' if cfg['SHOW_FUNCTION_OUTPUT'] else 'hide'})[/dim]",
209
+ rich_help_panel="Function Options",
210
+ show_default=False,
211
+ callback=override_config,
212
+ ),
173
213
  ):
174
214
  """YAICLI: Your AI assistant in the command line.
175
215
 
@@ -179,7 +219,7 @@ def main(
179
219
  print(DEFAULT_CONFIG_INI)
180
220
  raise typer.Exit()
181
221
 
182
- # Combine prompt argument with stdin content if available
222
+ # # Combine prompt argument with stdin content if available
183
223
  final_prompt = prompt
184
224
  if not sys.stdin.isatty():
185
225
  stdin_content = sys.stdin.read().strip()
@@ -194,40 +234,29 @@ def main(
194
234
  if chat:
195
235
  print("Warning: --chat is ignored when stdin was redirected.")
196
236
  chat = False
237
+ if not any([final_prompt, chat]):
238
+ print(ctx.get_help())
239
+ return
197
240
 
198
- # Basic validation for conflicting options or missing prompt
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
201
- typer.echo(ctx.get_help())
202
- raise typer.Exit()
203
-
204
- # Use build-in role for --shell or --code mode
241
+ # # Use build-in role for --shell or --code mode
205
242
  if role and role != DefaultRoleNames.DEFAULT and (shell or code):
206
243
  print("Warning: --role is ignored when --shell or --code is used.")
207
244
  role = DefaultRoleNames.DEFAULT
208
245
 
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)
215
-
216
- # Run the appropriate mode
217
- cli_instance.run(
218
- chat=chat,
219
- shell=shell,
220
- input=final_prompt,
221
- role=role,
222
- )
223
- except Exception as e:
224
- # Catch potential errors during CLI initialization or run
225
- print(f"An error occurred: {e}")
226
- if verbose:
227
- import traceback
228
-
229
- traceback.print_exc()
230
- raise typer.Exit(code=1)
246
+ from yaicli.cli import CLI
247
+
248
+ role = CLI.evaluate_role_name(code, shell, role)
249
+
250
+ # Instantiate the main CLI class with the specified role
251
+ cli = CLI(verbose=verbose, role=role)
252
+
253
+ # Run the appropriate mode
254
+ cli.run(
255
+ chat=chat,
256
+ shell=shell,
257
+ code=code,
258
+ user_input=final_prompt,
259
+ )
231
260
 
232
261
 
233
262
  if __name__ == "__main__":
yaicli/exceptions.py CHANGED
@@ -1,46 +1,18 @@
1
- class YAICLIException(Exception):
2
- """Base exception for YAICLI"""
1
+ class YaicliError(Exception):
2
+ """Base exception for yaicli"""
3
3
 
4
4
  def __init__(self, message: str):
5
5
  self.message = message
6
6
  super().__init__(self.message)
7
7
 
8
8
 
9
- ###################################
10
- # Role exceptions
11
- class RoleNotFoundError(YAICLIException):
12
- """Exception raised when a role is not found"""
9
+ class ChatSaveError(YaicliError):
10
+ """Error saving chat"""
13
11
 
14
- pass
15
12
 
13
+ class ChatLoadError(YaicliError):
14
+ """Error loading chat"""
16
15
 
17
- class RoleAlreadyExistsError(YAICLIException):
18
- """Exception raised when a role already exists"""
19
16
 
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
17
+ class ChatDeleteError(YaicliError):
18
+ """Error deleting chat"""
@@ -0,0 +1,39 @@
1
+ import shutil
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ from ..console import get_console
6
+ from ..const import FUNCTIONS_DIR
7
+ from ..utils import option_callback
8
+
9
+ console = get_console()
10
+
11
+
12
+ @option_callback
13
+ def install_functions(cls, _: Any) -> None:
14
+ """Install buildin functions"""
15
+ cur_dir = Path(__file__).absolute().parent
16
+ buildin_dir = cur_dir / "buildin"
17
+ buildin_funcs = [Path(path) for path in buildin_dir.glob("*.py")]
18
+ console.print("Installing buildin functions...")
19
+ if not FUNCTIONS_DIR.exists():
20
+ FUNCTIONS_DIR.mkdir(parents=True)
21
+ for file in buildin_funcs:
22
+ if (FUNCTIONS_DIR / file.name).exists():
23
+ # Skip if function already exists
24
+ console.print(f"Function {file.name} already exists, skipping.")
25
+ continue
26
+ shutil.copy(file, FUNCTIONS_DIR, follow_symlinks=True)
27
+ console.print(f"Installed {FUNCTIONS_DIR}/{file.name}")
28
+
29
+
30
+ @option_callback
31
+ def print_functions(cls, _: Any) -> None:
32
+ """List all available buildin functions"""
33
+ if not FUNCTIONS_DIR.exists():
34
+ console.print("No installed functions found.")
35
+ return
36
+ for file in FUNCTIONS_DIR.glob("*.py"):
37
+ if file.name.startswith("_"):
38
+ continue
39
+ console.print(file)
@@ -0,0 +1,47 @@
1
+ import subprocess
2
+
3
+ from instructor import OpenAISchema
4
+ from pydantic import Field
5
+
6
+
7
+ class Function(OpenAISchema):
8
+ """
9
+ Execute a shell command and return the output (result).
10
+ """
11
+
12
+ shell_command: str = Field(
13
+ ...,
14
+ json_schema_extra={
15
+ "example": "ls -la",
16
+ },
17
+ description="Shell command to execute.",
18
+ )
19
+
20
+ class Config:
21
+ title = "execute_shell_command"
22
+
23
+ @classmethod
24
+ def execute(cls, shell_command: str) -> str:
25
+ """
26
+ Execute a shell command and return the output (result).
27
+
28
+ Args:
29
+ shell_command (str): shell command to execute.
30
+
31
+ Returns:
32
+ str: exit code and output string.
33
+ """
34
+ # Optional security check
35
+ dangerous_commands = ["rm -rf", "mkfs", "dd"]
36
+ if any(cmd in shell_command for cmd in dangerous_commands):
37
+ return "Error: Dangerous command detected."
38
+
39
+ try:
40
+ process = subprocess.Popen(
41
+ shell_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
42
+ )
43
+ output, _ = process.communicate()
44
+ exit_code = process.returncode
45
+ return f"Exit code: {exit_code}, Output:\n{output}"
46
+ except Exception as e:
47
+ return f"Error: {str(e)}"