yaicli 0.4.0__py3-none-any.whl → 0.5.1__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
@@ -67,12 +67,19 @@ def main(
67
67
  max_tokens: int = typer.Option( # noqa: F841
68
68
  cfg["MAX_TOKENS"],
69
69
  "--max-tokens",
70
- "-M",
71
70
  help="Specify the max tokens to use.",
72
71
  rich_help_panel="LLM Options",
73
72
  min=1,
74
73
  callback=override_config,
75
74
  ),
75
+ stream: bool = typer.Option( # noqa: F841
76
+ cfg["STREAM"],
77
+ "--stream/--no-stream",
78
+ help=f"Specify whether to stream the response. [dim](default: {'stream' if cfg['STREAM'] else 'no-stream'})[/dim]",
79
+ rich_help_panel="LLM Options",
80
+ show_default=False,
81
+ callback=override_config,
82
+ ),
76
83
  # ------------------- Role Options -------------------
77
84
  role: str = typer.Option(
78
85
  DefaultRoleNames.DEFAULT,
@@ -118,7 +125,7 @@ def main(
118
125
  help="Start in interactive chat mode.",
119
126
  rich_help_panel="Chat Options",
120
127
  ),
121
- # ------------------- Shell Options -------------------
128
+ # # ------------------- Shell Options -------------------
122
129
  shell: bool = typer.Option(
123
130
  False,
124
131
  "--shell",
@@ -126,7 +133,7 @@ def main(
126
133
  help="Generate and optionally execute a shell command (non-interactive).",
127
134
  rich_help_panel="Shell Options",
128
135
  ),
129
- # ------------------- Code Options -------------------
136
+ # # ------------------- Code Options -------------------
130
137
  code: bool = typer.Option(
131
138
  False,
132
139
  "--code",
@@ -157,7 +164,8 @@ def main(
157
164
  ),
158
165
  show_reasoning: bool = typer.Option( # noqa: F841
159
166
  cfg["SHOW_REASONING"],
160
- help=f"Show reasoning content from the LLM. [dim](default: {cfg['SHOW_REASONING']})[/dim]",
167
+ "--show-reasoning/--hide-reasoning",
168
+ help=f"Show reasoning content from the LLM. [dim](default: {'show' if cfg['SHOW_REASONING'] else 'hide'})[/dim]",
161
169
  rich_help_panel="Other Options",
162
170
  show_default=False,
163
171
  callback=override_config,
@@ -170,6 +178,37 @@ def main(
170
178
  rich_help_panel="Other Options",
171
179
  callback=override_config,
172
180
  ),
181
+ # ------------------- Function Options -------------------
182
+ install_functions: bool = typer.Option( # noqa: F841
183
+ False,
184
+ "--install-functions",
185
+ help="Install default functions.",
186
+ rich_help_panel="Function Options",
187
+ callback=install_functions,
188
+ ),
189
+ list_functions: bool = typer.Option( # noqa: F841
190
+ False,
191
+ "--list-functions",
192
+ help="List all available functions.",
193
+ rich_help_panel="Function Options",
194
+ callback=print_functions,
195
+ ),
196
+ enable_functions: bool = typer.Option( # noqa: F841
197
+ cfg["ENABLE_FUNCTIONS"],
198
+ "--enable-functions/--disable-functions",
199
+ help=f"Enable/disable function calling in API requests [dim](default: {'enabled' if cfg['ENABLE_FUNCTIONS'] else 'disabled'})[/dim]",
200
+ rich_help_panel="Function Options",
201
+ show_default=False,
202
+ callback=override_config,
203
+ ),
204
+ show_function_output: bool = typer.Option( # noqa: F841
205
+ cfg["SHOW_FUNCTION_OUTPUT"],
206
+ "--show-function-output/--hide-function-output",
207
+ help=f"Show the output of functions [dim](default: {'show' if cfg['SHOW_FUNCTION_OUTPUT'] else 'hide'})[/dim]",
208
+ rich_help_panel="Function Options",
209
+ show_default=False,
210
+ callback=override_config,
211
+ ),
173
212
  ):
174
213
  """YAICLI: Your AI assistant in the command line.
175
214
 
@@ -179,7 +218,7 @@ def main(
179
218
  print(DEFAULT_CONFIG_INI)
180
219
  raise typer.Exit()
181
220
 
182
- # Combine prompt argument with stdin content if available
221
+ # # Combine prompt argument with stdin content if available
183
222
  final_prompt = prompt
184
223
  if not sys.stdin.isatty():
185
224
  stdin_content = sys.stdin.read().strip()
@@ -194,40 +233,29 @@ def main(
194
233
  if chat:
195
234
  print("Warning: --chat is ignored when stdin was redirected.")
196
235
  chat = False
236
+ if not any([final_prompt, chat]):
237
+ print(ctx.get_help())
238
+ return
197
239
 
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
240
+ # # Use build-in role for --shell or --code mode
205
241
  if role and role != DefaultRoleNames.DEFAULT and (shell or code):
206
242
  print("Warning: --role is ignored when --shell or --code is used.")
207
243
  role = DefaultRoleNames.DEFAULT
208
244
 
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)
245
+ from yaicli.cli import CLI
246
+
247
+ role = CLI.evaluate_role_name(code, shell, role)
248
+
249
+ # Instantiate the main CLI class with the specified role
250
+ cli = CLI(verbose=verbose, role=role)
251
+
252
+ # Run the appropriate mode
253
+ cli.run(
254
+ chat=chat,
255
+ shell=shell,
256
+ code=code,
257
+ user_input=final_prompt,
258
+ )
231
259
 
232
260
 
233
261
  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)}"