yaicli 0.3.3__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.
- pyproject.toml +6 -3
- yaicli/chat.py +396 -0
- yaicli/cli.py +251 -251
- yaicli/client.py +385 -0
- yaicli/config.py +32 -20
- yaicli/console.py +2 -2
- yaicli/const.py +46 -21
- yaicli/entry.py +68 -39
- yaicli/exceptions.py +8 -36
- yaicli/functions/__init__.py +39 -0
- yaicli/functions/buildin/execute_shell_command.py +47 -0
- yaicli/printer.py +145 -225
- yaicli/render.py +1 -1
- yaicli/role.py +231 -0
- yaicli/schemas.py +31 -0
- yaicli/tools.py +103 -0
- yaicli/utils.py +5 -2
- {yaicli-0.3.3.dist-info → yaicli-0.5.0.dist-info}/METADATA +172 -132
- yaicli-0.5.0.dist-info/RECORD +24 -0
- {yaicli-0.3.3.dist-info → yaicli-0.5.0.dist-info}/entry_points.txt +1 -1
- yaicli/api.py +0 -316
- yaicli/chat_manager.py +0 -290
- yaicli/roles.py +0 -248
- yaicli-0.3.3.dist-info/RECORD +0 -20
- {yaicli-0.3.3.dist-info → yaicli-0.5.0.dist-info}/WHEEL +0 -0
- {yaicli-0.3.3.dist-info → yaicli-0.5.0.dist-info}/licenses/LICENSE +0 -0
yaicli/const.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
from enum import StrEnum
|
2
2
|
from pathlib import Path
|
3
3
|
from tempfile import gettempdir
|
4
|
-
from typing import Any
|
4
|
+
from typing import Any, Literal, Optional
|
5
5
|
|
6
6
|
from rich.console import JustifyMethod
|
7
7
|
|
8
|
+
BOOL_STR = Literal["true", "false", "yes", "no", "y", "n", "1", "0", "on", "off"]
|
9
|
+
|
8
10
|
|
9
11
|
class JustifyEnum(StrEnum):
|
10
12
|
DEFAULT = "default"
|
@@ -22,36 +24,41 @@ CMD_SAVE_CHAT = "/save"
|
|
22
24
|
CMD_LOAD_CHAT = "/load"
|
23
25
|
CMD_LIST_CHATS = "/list"
|
24
26
|
CMD_DELETE_CHAT = "/del"
|
27
|
+
CMD_HELP = ("/help", "?")
|
25
28
|
|
26
29
|
EXEC_MODE = "exec"
|
27
30
|
CHAT_MODE = "chat"
|
28
31
|
TEMP_MODE = "temp"
|
29
32
|
CODE_MODE = "code"
|
30
33
|
|
34
|
+
HISTORY_FILE = Path("~/.yaicli_history").expanduser()
|
31
35
|
CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
|
32
36
|
ROLES_DIR = CONFIG_PATH.parent / "roles"
|
37
|
+
FUNCTIONS_DIR = CONFIG_PATH.parent / "functions"
|
33
38
|
|
34
39
|
# Default configuration values
|
35
40
|
DEFAULT_CODE_THEME = "monokai"
|
36
|
-
DEFAULT_COMPLETION_PATH = "chat/completions"
|
37
|
-
DEFAULT_ANSWER_PATH = "choices[0].message.content"
|
38
41
|
DEFAULT_PROVIDER = "openai"
|
39
42
|
DEFAULT_BASE_URL = "https://api.openai.com/v1"
|
40
43
|
DEFAULT_MODEL = "gpt-4o"
|
41
44
|
DEFAULT_SHELL_NAME = "auto"
|
42
45
|
DEFAULT_OS_NAME = "auto"
|
43
|
-
DEFAULT_STREAM = "true"
|
44
|
-
DEFAULT_TEMPERATURE: float = 0.
|
46
|
+
DEFAULT_STREAM: BOOL_STR = "true"
|
47
|
+
DEFAULT_TEMPERATURE: float = 0.5
|
45
48
|
DEFAULT_TOP_P: float = 1.0
|
46
49
|
DEFAULT_MAX_TOKENS: int = 1024
|
47
50
|
DEFAULT_MAX_HISTORY: int = 500
|
48
|
-
DEFAULT_AUTO_SUGGEST = "true"
|
49
|
-
DEFAULT_SHOW_REASONING = "true"
|
51
|
+
DEFAULT_AUTO_SUGGEST: BOOL_STR = "true"
|
52
|
+
DEFAULT_SHOW_REASONING: BOOL_STR = "true"
|
50
53
|
DEFAULT_TIMEOUT: int = 60
|
51
54
|
DEFAULT_INTERACTIVE_ROUND: int = 25
|
52
|
-
DEFAULT_CHAT_HISTORY_DIR = Path(gettempdir()) / "yaicli/chats"
|
55
|
+
DEFAULT_CHAT_HISTORY_DIR: Path = Path(gettempdir()) / "yaicli/chats"
|
53
56
|
DEFAULT_MAX_SAVED_CHATS = 20
|
54
57
|
DEFAULT_JUSTIFY: JustifyMethod = "default"
|
58
|
+
DEFAULT_ROLE_MODIFY_WARNING: BOOL_STR = "true"
|
59
|
+
DEFAULT_ENABLE_FUNCTIONS: BOOL_STR = "true"
|
60
|
+
DEFAULT_SHOW_FUNCTION_OUTPUT: BOOL_STR = "true"
|
61
|
+
DEFAULT_REASONING_EFFORT: Optional[Literal["low", "high", "medium"]] = None
|
55
62
|
|
56
63
|
|
57
64
|
class EventTypeEnum(StrEnum):
|
@@ -62,14 +69,18 @@ class EventTypeEnum(StrEnum):
|
|
62
69
|
REASONING_END = "reasoning_end"
|
63
70
|
CONTENT = "content"
|
64
71
|
FINISH = "finish"
|
72
|
+
TOOL_CALL_START = "tool_call_start"
|
73
|
+
TOOL_CALL_DELTA = "tool_call_delta"
|
74
|
+
TOOL_CALL_END = "tool_call_end"
|
75
|
+
TOOL_RESULT = "tool_result"
|
76
|
+
TOOL_CALLS_FINISH = "tool_calls_finish"
|
65
77
|
|
66
78
|
|
67
|
-
SHELL_PROMPT = """
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
Supply plain text only, avoiding Markdown formatting."""
|
79
|
+
SHELL_PROMPT = """You are YAICLI, a shell command generator.
|
80
|
+
The context conversation may contain other types of messages,
|
81
|
+
but you should only respond with a single valid {_shell} shell command for {_os}.
|
82
|
+
Do not include any explanations, comments, or formatting — only the command as plain text, avoiding Markdown formatting.
|
83
|
+
"""
|
73
84
|
|
74
85
|
DEFAULT_PROMPT = """
|
75
86
|
You are YAICLI, a system management and programing assistant,
|
@@ -113,15 +124,13 @@ DEFAULT_CONFIG_MAP = {
|
|
113
124
|
# System detection hints
|
114
125
|
"SHELL_NAME": {"value": DEFAULT_SHELL_NAME, "env_key": "YAI_SHELL_NAME", "type": str},
|
115
126
|
"OS_NAME": {"value": DEFAULT_OS_NAME, "env_key": "YAI_OS_NAME", "type": str},
|
116
|
-
# API paths (usually no need to change for OpenAI compatible APIs)
|
117
|
-
"COMPLETION_PATH": {"value": DEFAULT_COMPLETION_PATH, "env_key": "YAI_COMPLETION_PATH", "type": str},
|
118
|
-
"ANSWER_PATH": {"value": DEFAULT_ANSWER_PATH, "env_key": "YAI_ANSWER_PATH", "type": str},
|
119
127
|
# API call parameters
|
120
128
|
"STREAM": {"value": DEFAULT_STREAM, "env_key": "YAI_STREAM", "type": bool},
|
121
129
|
"TEMPERATURE": {"value": DEFAULT_TEMPERATURE, "env_key": "YAI_TEMPERATURE", "type": float},
|
122
130
|
"TOP_P": {"value": DEFAULT_TOP_P, "env_key": "YAI_TOP_P", "type": float},
|
123
131
|
"MAX_TOKENS": {"value": DEFAULT_MAX_TOKENS, "env_key": "YAI_MAX_TOKENS", "type": int},
|
124
132
|
"TIMEOUT": {"value": DEFAULT_TIMEOUT, "env_key": "YAI_TIMEOUT", "type": int},
|
133
|
+
"REASONING_EFFORT": {"value": DEFAULT_REASONING_EFFORT, "env_key": "YAI_REASONING_EFFORT", "type": str},
|
125
134
|
"INTERACTIVE_ROUND": {
|
126
135
|
"value": DEFAULT_INTERACTIVE_ROUND,
|
127
136
|
"env_key": "YAI_INTERACTIVE_ROUND",
|
@@ -136,6 +145,15 @@ DEFAULT_CONFIG_MAP = {
|
|
136
145
|
# Chat history settings
|
137
146
|
"CHAT_HISTORY_DIR": {"value": DEFAULT_CHAT_HISTORY_DIR, "env_key": "YAI_CHAT_HISTORY_DIR", "type": str},
|
138
147
|
"MAX_SAVED_CHATS": {"value": DEFAULT_MAX_SAVED_CHATS, "env_key": "YAI_MAX_SAVED_CHATS", "type": int},
|
148
|
+
# Role settings
|
149
|
+
"ROLE_MODIFY_WARNING": {"value": DEFAULT_ROLE_MODIFY_WARNING, "env_key": "YAI_ROLE_MODIFY_WARNING", "type": bool},
|
150
|
+
# Function settings
|
151
|
+
"ENABLE_FUNCTIONS": {"value": DEFAULT_ENABLE_FUNCTIONS, "env_key": "YAI_ENABLE_FUNCTIONS", "type": bool},
|
152
|
+
"SHOW_FUNCTION_OUTPUT": {
|
153
|
+
"value": DEFAULT_SHOW_FUNCTION_OUTPUT,
|
154
|
+
"env_key": "YAI_SHOW_FUNCTION_OUTPUT",
|
155
|
+
"type": bool,
|
156
|
+
},
|
139
157
|
}
|
140
158
|
|
141
159
|
DEFAULT_CONFIG_INI = f"""[core]
|
@@ -148,10 +166,6 @@ MODEL={DEFAULT_CONFIG_MAP["MODEL"]["value"]}
|
|
148
166
|
SHELL_NAME={DEFAULT_CONFIG_MAP["SHELL_NAME"]["value"]}
|
149
167
|
OS_NAME={DEFAULT_CONFIG_MAP["OS_NAME"]["value"]}
|
150
168
|
|
151
|
-
# API paths (usually no need to change for OpenAI compatible APIs)
|
152
|
-
COMPLETION_PATH={DEFAULT_CONFIG_MAP["COMPLETION_PATH"]["value"]}
|
153
|
-
ANSWER_PATH={DEFAULT_CONFIG_MAP["ANSWER_PATH"]["value"]}
|
154
|
-
|
155
169
|
# true: streaming response, false: non-streaming
|
156
170
|
STREAM={DEFAULT_CONFIG_MAP["STREAM"]["value"]}
|
157
171
|
|
@@ -160,6 +174,7 @@ TEMPERATURE={DEFAULT_CONFIG_MAP["TEMPERATURE"]["value"]}
|
|
160
174
|
TOP_P={DEFAULT_CONFIG_MAP["TOP_P"]["value"]}
|
161
175
|
MAX_TOKENS={DEFAULT_CONFIG_MAP["MAX_TOKENS"]["value"]}
|
162
176
|
TIMEOUT={DEFAULT_CONFIG_MAP["TIMEOUT"]["value"]}
|
177
|
+
REASONING_EFFORT=
|
163
178
|
|
164
179
|
# Interactive mode parameters
|
165
180
|
INTERACTIVE_ROUND={DEFAULT_CONFIG_MAP["INTERACTIVE_ROUND"]["value"]}
|
@@ -177,4 +192,14 @@ JUSTIFY={DEFAULT_CONFIG_MAP["JUSTIFY"]["value"]}
|
|
177
192
|
# Chat history settings
|
178
193
|
CHAT_HISTORY_DIR={DEFAULT_CONFIG_MAP["CHAT_HISTORY_DIR"]["value"]}
|
179
194
|
MAX_SAVED_CHATS={DEFAULT_CONFIG_MAP["MAX_SAVED_CHATS"]["value"]}
|
195
|
+
|
196
|
+
# Role settings
|
197
|
+
# Set to false to disable warnings about modified built-in roles
|
198
|
+
ROLE_MODIFY_WARNING={DEFAULT_CONFIG_MAP["ROLE_MODIFY_WARNING"]["value"]}
|
199
|
+
|
200
|
+
# Function settings
|
201
|
+
# Set to false to disable sending functions in API requests
|
202
|
+
ENABLE_FUNCTIONS={DEFAULT_CONFIG_MAP["ENABLE_FUNCTIONS"]["value"]}
|
203
|
+
# Set to false to disable showing function output in the response
|
204
|
+
SHOW_FUNCTION_OUTPUT={DEFAULT_CONFIG_MAP["SHOW_FUNCTION_OUTPUT"]["value"]}
|
180
205
|
"""
|
yaicli/entry.py
CHANGED
@@ -3,15 +3,15 @@ from typing import Annotated, Any, Optional
|
|
3
3
|
|
4
4
|
import typer
|
5
5
|
|
6
|
-
from
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from
|
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 -
|
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
|
-
|
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
|
-
#
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|
2
|
-
"""Base exception for
|
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
|
-
|
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
|
-
|
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)}"
|