yaicli 0.0.7__py3-none-any.whl → 0.0.8__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 ADDED
@@ -0,0 +1,66 @@
1
+ [project]
2
+ name = "yaicli"
3
+ version = "0.0.8"
4
+ description = "A simple CLI tool to interact with LLM"
5
+ authors = [{ name = "belingud", email = "im.victor@qq.com" }]
6
+ readme = "README.md"
7
+ requires-python = ">=3.9"
8
+ license = { file = "LICENSE" }
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "License :: OSI Approved :: MIT License",
12
+ "Operating System :: OS Independent",
13
+ ]
14
+ keywords = [
15
+ "cli",
16
+ "llm",
17
+ "ai",
18
+ "chatgpt",
19
+ "openai",
20
+ "gpt",
21
+ "llms",
22
+ "openai",
23
+ "terminal",
24
+ "interactive",
25
+ "interact",
26
+ "interact with llm",
27
+ "interact with chatgpt",
28
+ "interact with openai",
29
+ "interact with gpt",
30
+ "interact with llms",
31
+ ]
32
+ dependencies = [
33
+ "distro>=1.9.0",
34
+ "jmespath>=1.0.1",
35
+ "prompt-toolkit>=3.0.50",
36
+ "requests>=2.32.3",
37
+ "rich>=13.9.4",
38
+ "typer>=0.15.2",
39
+ ]
40
+ [project.urls]
41
+ Homepage = "https://github.com/belingud/yaicli"
42
+ Repository = "https://github.com/belingud/yaicli"
43
+ Documentation = "https://github.com/belingud/yaicli"
44
+
45
+ [project.scripts]
46
+ ai = "yaicli:app"
47
+
48
+ [tool.uv]
49
+ resolution = "highest"
50
+
51
+ [dependency-groups]
52
+ dev = ["bump2version>=1.0.1", "pytest>=8.3.5", "ruff>=0.11.2"]
53
+
54
+ [tool.ruff]
55
+ line-length = 120
56
+ select = ["E", "F", "W", "I", "B", "C90"]
57
+ ignore = ["E501"]
58
+
59
+
60
+ [build-system]
61
+ requires = ["hatchling>=1.18.0"]
62
+ build-backend = "hatchling.build"
63
+
64
+ [tool.hatch.build]
65
+ exclude = ["test_*.py", "tests/*", ".gitignore"]
66
+ include = ["yaicli.py", "pyproject.toml"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaicli
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: A simple CLI tool to interact with LLM
5
5
  Project-URL: Homepage, https://github.com/belingud/yaicli
6
6
  Project-URL: Repository, https://github.com/belingud/yaicli
@@ -223,6 +223,11 @@ Description-Content-Type: text/markdown
223
223
 
224
224
  # YAICLI - Your AI Command Line Interface
225
225
 
226
+ [![PyPI version](https://img.shields.io/pypi/v/yaicli?style=for-the-badge)](https://pypi.org/project/yaicli/)
227
+ ![GitHub License](https://img.shields.io/github/license/belingud/yaicli?style=for-the-badge)
228
+ ![PyPI - Downloads](https://img.shields.io/pypi/dm/yaicli?logo=pypi&style=for-the-badge)
229
+ ![Pepy Total Downloads](https://img.shields.io/pepy/dt/yaicli?style=for-the-badge&logo=python)
230
+
226
231
  YAICLI is a powerful command-line AI assistant tool that enables you to interact with Large Language Models (LLMs) like ChatGPT's gpt-4o through your terminal. It offers multiple operation modes for everyday conversations, generating and executing shell commands, and one-shot quick queries.
227
232
 
228
233
  > [!WARNING]
@@ -0,0 +1,7 @@
1
+ pyproject.toml,sha256=0ri15toylKhtFhwAemgdMReD72wetPZIISWhJtpsqe8,1450
2
+ yaicli.py,sha256=E6QRi9KgMBoDU-KQ1V7N1SVTAg5PIVIfCMWXLn3taE4,16877
3
+ yaicli-0.0.8.dist-info/METADATA,sha256=OIArk-08C1L-hW8Hu9RfGby7PVm0G_C16_DA_peaoxM,23489
4
+ yaicli-0.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ yaicli-0.0.8.dist-info/entry_points.txt,sha256=gdduQwAuu_LeDqnDU81Fv3NPmD2tRQ1FffvolIP3S1Q,34
6
+ yaicli-0.0.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
+ yaicli-0.0.8.dist-info/RECORD,,
yaicli.py CHANGED
@@ -71,6 +71,7 @@ class CasePreservingConfigParser(configparser.RawConfigParser):
71
71
  class CLI:
72
72
  CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
73
73
  DEFAULT_CONFIG_INI = """[core]
74
+ PROVIDER=openai
74
75
  BASE_URL=https://api.openai.com/v1
75
76
  API_KEY=
76
77
  MODEL=gpt-4o
@@ -166,6 +167,50 @@ STREAM=true"""
166
167
  return "powershell.exe" if is_powershell else "cmd.exe"
167
168
  return basename(getenv("SHELL", "/bin/sh"))
168
169
 
170
+ def _filter_command(self, command: str) -> Optional[str]:
171
+ """Filter out unwanted characters from command
172
+
173
+ The LLM may return commands in markdown format with code blocks.
174
+ This method removes markdown formatting from the command.
175
+ It handles various formats including:
176
+ - Commands surrounded by ``` (plain code blocks)
177
+ - Commands with language specifiers like ```bash, ```zsh, etc.
178
+ - Commands with specific examples like ```ls -al```
179
+
180
+ example:
181
+ ```bash\nls -la\n``` ==> ls -al
182
+ ```zsh\nls -la\n``` ==> ls -al
183
+ ```ls -al``` ==> ls -al
184
+ ls -al ==> ls -al
185
+ ```\ncd /tmp\nls -la\n``` ==> cd /tmp\nls -la
186
+ ```bash\ncd /tmp\nls -la\n``` ==> cd /tmp\nls -la
187
+ """
188
+ if not command or not command.strip():
189
+ return ""
190
+
191
+ # Handle commands that are already without code blocks
192
+ if "```" not in command:
193
+ return command.strip()
194
+
195
+ # Handle code blocks with or without language specifiers
196
+ lines = command.strip().split("\n")
197
+
198
+ # Check if it's a single-line code block like ```ls -al```
199
+ if len(lines) == 1 and lines[0].startswith("```") and lines[0].endswith("```"):
200
+ return lines[0][3:-3].strip()
201
+
202
+ # Handle multi-line code blocks
203
+ if lines[0].startswith("```"):
204
+ # Remove the opening ``` line (with or without language specifier)
205
+ content_lines = lines[1:]
206
+
207
+ # If the last line is a closing ```, remove it
208
+ if content_lines and content_lines[-1].strip() == "```":
209
+ content_lines = content_lines[:-1]
210
+
211
+ # Join the remaining lines and strip any extra whitespace
212
+ return "\n".join(line.strip() for line in content_lines if line.strip())
213
+
169
214
  def post(self, message: list[dict[str, str]]) -> requests.Response:
170
215
  """Post message to LLM API and return response"""
171
216
  url = self.config.get("BASE_URL", "").rstrip("/") + "/" + self.config.get("COMPLETION_PATH", "").lstrip("/")
@@ -238,6 +283,11 @@ STREAM=true"""
238
283
  qmark = ""
239
284
  return [("class:qmark", qmark), ("class:question", " {} ".format(">"))]
240
285
 
286
+ def _check_history_len(self) -> None:
287
+ """Check history length and remove oldest messages if necessary"""
288
+ if len(self.history) > self.max_history_length:
289
+ self.history = self.history[-self.max_history_length :]
290
+
241
291
  def _run_repl(self) -> None:
242
292
  """Run REPL loop, handling user input and generating responses, saving history, and executing commands"""
243
293
  # Show REPL instructions
@@ -280,27 +330,33 @@ STREAM=true"""
280
330
  # Get response from LLM
281
331
  response = self.post(message)
282
332
  self.console.print("\n[bold green]Assistant:[/bold green]")
283
- content = self._print(response, stream=self.config["STREAM"] == "true")
333
+ try:
334
+ content = self._print(response, stream=self.config["STREAM"] == "true")
335
+ except Exception as e:
336
+ self.console.print(f"[red]Error: {e}[/red]")
337
+ continue
284
338
 
285
339
  # Add user input and assistant response to history
286
340
  self.history.append({"role": "user", "content": user_input})
287
341
  self.history.append({"role": "assistant", "content": content})
288
342
 
289
343
  # Trim history if needed
290
- if len(self.history) > self.max_history_length * 2:
291
- self.history = self.history[-self.max_history_length * 2 :]
344
+ self._check_history_len()
292
345
 
293
346
  # Handle command execution in exec mode
294
347
  if self.current_mode == EXEC_MODE:
348
+ content = self._filter_command(content)
349
+ if not content:
350
+ self.console.print("[bold red]No command generated[/bold red]")
351
+ continue
295
352
  self.console.print(f"\n[bold magenta]Generated command:[/bold magenta] {content}")
296
353
  if Confirm.ask("Execute this command?", default=False):
297
- returncode = subprocess.call(content, shell=True)
298
- if returncode != 0:
299
- self.console.print(f"[bold red]Command failed with return code {returncode}[/bold red]")
354
+ subprocess.call(content, shell=True)
300
355
 
301
356
  self.console.print("[bold green]Exiting...[/bold green]")
302
357
 
303
358
  def run(self, chat: bool, shell: bool, prompt: str) -> None:
359
+ """Run the CLI"""
304
360
  self.load_config()
305
361
  if not self.config.get("API_KEY"):
306
362
  self.console.print("[bold red]API key not set[/bold red]")
@@ -330,10 +386,14 @@ STREAM=true"""
330
386
  # Get response from LLM
331
387
  response = self.post(message)
332
388
  self.console.print("\n[bold green]Assistant:[/bold green]")
333
- content = self._print(response, stream=(not shell and self.config["STREAM"] == "true"))
389
+ content = self._print(response, stream=self.config["STREAM"] == "true")
334
390
 
335
391
  # Handle shell mode execution
336
392
  if shell:
393
+ content = self._filter_command(content)
394
+ if not content:
395
+ self.console.print("[bold red]No command generated[/bold red]")
396
+ return
337
397
  self.console.print(f"\n[bold magenta]Generated command:[/bold magenta] {content}")
338
398
  if Confirm.ask("Execute this command?", default=False):
339
399
  returncode = subprocess.call(content, shell=True)
@@ -1,6 +0,0 @@
1
- yaicli.py,sha256=Wsi6VWGw3FfCkl8d6AR6p3nY8QbdZM0bx8JXFNrfZP8,14538
2
- yaicli-0.0.7.dist-info/METADATA,sha256=EHpi_s3X6a31V6z2EuGQ9hOgh1Karxi07f-H6Vbeekg,23101
3
- yaicli-0.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
4
- yaicli-0.0.7.dist-info/entry_points.txt,sha256=gdduQwAuu_LeDqnDU81Fv3NPmD2tRQ1FffvolIP3S1Q,34
5
- yaicli-0.0.7.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
6
- yaicli-0.0.7.dist-info/RECORD,,
File without changes