yaicli 0.0.15__py3-none-any.whl → 0.0.17__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 CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "yaicli"
3
- version = "0.0.15"
3
+ version = "0.0.17"
4
4
  description = "A simple CLI tool to interact with LLM"
5
5
  authors = [{ name = "belingud", email = "im.victor@qq.com" }]
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaicli
3
- Version: 0.0.15
3
+ Version: 0.0.17
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
@@ -259,6 +259,8 @@ Support regular and deep thinking models.
259
259
 
260
260
  - **Keyboard Shortcuts**:
261
261
  - Tab to switch between Chat and Execute modes
262
+ - `↑/↓` to navigate history
263
+ - `Ctrl+R` to search history
262
264
 
263
265
  - **History**:
264
266
  - Save and recall previous queries
@@ -342,6 +344,7 @@ Below are the available configuration options and override environment variables
342
344
  - **TEMPERATURE**: Temperature for response generation (default: 0.7), env: YAI_TEMPERATURE
343
345
  - **TOP_P**: Top-p sampling for response generation (default: 1.0), env: YAI_TOP_P
344
346
  - **MAX_TOKENS**: Maximum number of tokens for response generation (default: 1024), env: YAI_MAX_TOKENS
347
+ - **MAX_HISTORY**: Max history size, default: 500, env: YAI_MAX_HISTORY
345
348
 
346
349
  Default config of `COMPLETION_PATH` and `ANSWER_PATH` is OpenAI compatible. If you are using OpenAI or other OpenAI compatible LLM provider, you can use the default config.
347
350
 
@@ -399,7 +402,7 @@ If you not sure how to config `COMPLETION_PATH` and `ANSWER_PATH`, here is a gui
399
402
  You can find the list of code theme here: https://pygments.org/styles/
400
403
 
401
404
  Default: monokia
402
- ![alt text](artwork/monokia.png)
405
+ ![monikia](artwork/monokia.png)
403
406
 
404
407
  ## Usage
405
408
 
@@ -476,6 +479,25 @@ In Execute mode:
476
479
  3. Review the command
477
480
  4. Confirm to execute or reject
478
481
 
482
+ ### Keyboard Shortcuts
483
+ - `Tab`: Switch between Chat and Execute modes
484
+ - `Ctrl+C`: Exit
485
+ - `Ctrl+R`: Search history
486
+ - `↑/↓`: Navigate history
487
+
488
+ ### Stdin
489
+ You can also pipe input to YAICLI:
490
+ ```bash
491
+ echo "What is the capital of France?" | ai
492
+ ```
493
+
494
+ ```bash
495
+ cat demo.py | ai "How to use this tool?"
496
+ ```
497
+
498
+ ### History
499
+ Support max history size. Set MAX_HISTORY in config file. Default is 500.
500
+
479
501
  ## Examples
480
502
 
481
503
  ### Have a Chat
@@ -574,7 +596,7 @@ YAICLI is built using several Python libraries:
574
596
  - **Typer**: Provides the command-line interface
575
597
  - **Rich**: Provides terminal content formatting and beautiful display
576
598
  - **prompt_toolkit**: Provides interactive command-line input experience
577
- - **requests**: Handles API requests
599
+ - **httpx**: Handles API requests
578
600
  - **jmespath**: Parses JSON responses
579
601
 
580
602
  ## Contributing
@@ -0,0 +1,7 @@
1
+ pyproject.toml,sha256=n7Dd2aKGwVW-et5Vo8T7Ti6taJDglKMcYC7bOkkcgBg,1452
2
+ yaicli.py,sha256=bvr4Mr6otWVgrmHbbPOnYXIINBz8NxfQl2AvbcjQ0Bc,25156
3
+ yaicli-0.0.17.dist-info/METADATA,sha256=iMFag80paztV2vcgO_Xs6cuxw8nLXQ4xeSWavPJ8ny4,29932
4
+ yaicli-0.0.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ yaicli-0.0.17.dist-info/entry_points.txt,sha256=gdduQwAuu_LeDqnDU81Fv3NPmD2tRQ1FffvolIP3S1Q,34
6
+ yaicli-0.0.17.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
+ yaicli-0.0.17.dist-info/RECORD,,
yaicli.py CHANGED
@@ -2,9 +2,10 @@ import configparser
2
2
  import json
3
3
  import platform
4
4
  import subprocess
5
+ import sys
5
6
  import time
6
7
  from os import getenv
7
- from os.path import basename, pathsep
8
+ from os.path import basename, exists, pathsep, devnull
8
9
  from pathlib import Path
9
10
  from typing import Annotated, Optional, Union
10
11
 
@@ -13,8 +14,8 @@ import jmespath
13
14
  import typer
14
15
  from distro import name as distro_name
15
16
  from prompt_toolkit import PromptSession, prompt
16
- from prompt_toolkit.completion import WordCompleter
17
- from prompt_toolkit.history import FileHistory
17
+ # from prompt_toolkit.completion import WordCompleter
18
+ from prompt_toolkit.history import FileHistory, _StrOrBytesPath
18
19
  from prompt_toolkit.key_binding import KeyBindings, KeyPressEvent
19
20
  from prompt_toolkit.keys import Keys
20
21
  from rich.console import Console
@@ -60,6 +61,7 @@ DEFAULT_CONFIG_MAP = {
60
61
  "TEMPERATURE": {"value": "0.7", "env_key": "YAI_TEMPERATURE"},
61
62
  "TOP_P": {"value": "1.0", "env_key": "YAI_TOP_P"},
62
63
  "MAX_TOKENS": {"value": "1024", "env_key": "YAI_MAX_TOKENS"},
64
+ "MAX_HISTORY": {"value": "500", "env_key": "YAI_MAX_HISTORY"},
63
65
  }
64
66
 
65
67
  DEFAULT_CONFIG_INI = """[core]
@@ -84,7 +86,9 @@ CODE_THEME=monokia
84
86
 
85
87
  TEMPERATURE=0.7
86
88
  TOP_P=1.0
87
- MAX_TOKENS=1024"""
89
+ MAX_TOKENS=1024
90
+
91
+ MAX_HISTORY=500"""
88
92
 
89
93
  app = typer.Typer(
90
94
  name="yaicli",
@@ -100,6 +104,64 @@ class CasePreservingConfigParser(configparser.RawConfigParser):
100
104
  return optionstr
101
105
 
102
106
 
107
+ class LimitedFileHistory(FileHistory):
108
+ def __init__(self, filename: _StrOrBytesPath, max_entries: int = 500, trim_every: int = 2):
109
+ """Limited file history
110
+ Args:
111
+ filename (str): path to history file
112
+ max_entries (int): maximum number of entries to keep
113
+ trim_every (int): trim history every `trim_every` appends
114
+
115
+ Example:
116
+ >>> history = LimitedFileHistory("~/.yaicli_history", max_entries=500, trim_every=10)
117
+ >>> history.append_string("echo hello")
118
+ >>> history.append_string("echo world")
119
+ >>> session = PromptSession(history=history)
120
+ """
121
+ self.max_entries = max_entries
122
+ self._append_count = 0
123
+ self._trim_every = trim_every
124
+ super().__init__(filename)
125
+
126
+ def store_string(self, string: str) -> None:
127
+ # Call the original method to deposit a new record
128
+ super().store_string(string)
129
+
130
+ self._append_count += 1
131
+ if self._append_count >= self._trim_every:
132
+ self._trim_history()
133
+ self._append_count = 0
134
+
135
+ def _trim_history(self):
136
+ if not exists(self.filename):
137
+ return
138
+
139
+ with open(self.filename, "r", encoding="utf-8") as f:
140
+ lines = f.readlines()
141
+
142
+ # By record: each record starts with "# timestamp" followed by a number of "+lines".
143
+ entries = []
144
+ current_entry = []
145
+
146
+ for line in lines:
147
+ if line.startswith("# "):
148
+ if current_entry:
149
+ entries.append(current_entry)
150
+ current_entry = [line]
151
+ elif line.startswith("+") or line.strip() == "":
152
+ current_entry.append(line)
153
+
154
+ if current_entry:
155
+ entries.append(current_entry)
156
+
157
+ # Keep the most recent max_entries row (the next row is newer)
158
+ trimmed_entries = entries[-self.max_entries :]
159
+
160
+ with open(self.filename, "w", encoding="utf-8") as f:
161
+ for entry in trimmed_entries:
162
+ f.writelines(entry)
163
+
164
+
103
165
  class CLI:
104
166
  CONFIG_PATH = Path("~/.config/yaicli/config.ini").expanduser()
105
167
 
@@ -107,7 +169,16 @@ class CLI:
107
169
  self.verbose = verbose
108
170
  self.console = Console()
109
171
  self.bindings = KeyBindings()
172
+ # Disable nonatty warning
173
+ _origin_stderr = None
174
+ if not sys.stdin.isatty():
175
+ _origin_stderr = sys.stderr
176
+ sys.stderr = open(devnull, "w")
110
177
  self.session = PromptSession(key_bindings=self.bindings)
178
+ # Restore stderr
179
+ if _origin_stderr:
180
+ sys.stderr.close()
181
+ sys.stderr = _origin_stderr
111
182
  self.config = {}
112
183
  self.history: list[dict[str, str]] = []
113
184
  self.max_history_length = 25
@@ -124,9 +195,11 @@ class CLI:
124
195
  Path("~/.yaicli_history").expanduser().touch(exist_ok=True)
125
196
  self.session = PromptSession(
126
197
  key_bindings=self.bindings,
127
- completer=WordCompleter(["/clear", "/exit", "/his"]),
198
+ # completer=WordCompleter(["/clear", "/exit", "/his"]),
128
199
  complete_while_typing=True,
129
- history=FileHistory(Path("~/.yaicli_history").expanduser()),
200
+ history=LimitedFileHistory(
201
+ Path("~/.yaicli_history").expanduser(), max_entries=int(self.config["MAX_HISTORY"])
202
+ ),
130
203
  enable_history_search=True,
131
204
  )
132
205
 
@@ -287,8 +360,7 @@ class CLI:
287
360
  if not line:
288
361
  return None
289
362
 
290
- if isinstance(line, bytes):
291
- line = line.decode("utf-8")
363
+ line = str(line)
292
364
  if not line.startswith("data: "):
293
365
  return None
294
366
 
@@ -448,6 +520,7 @@ class CLI:
448
520
  ██ ██ ██ ██ ██ ██ ██
449
521
  ██ ██ ██ ██ ██████ ███████ ██
450
522
  """)
523
+ self.console.print("↑/↓: navigate in history")
451
524
  self.console.print("Press TAB to change in chat and exec mode", style="bold")
452
525
  self.console.print("Type /clear to clear chat history", style="bold")
453
526
  self.console.print("Type /his to see chat history", style="bold")
@@ -490,6 +563,18 @@ class CLI:
490
563
  def run(self, chat: bool, shell: bool, prompt: str) -> None:
491
564
  """Run the CLI"""
492
565
  self.load_config()
566
+ if self.verbose:
567
+ self.console.print(f"CODE_THEME: {self.config['CODE_THEME']}")
568
+ self.console.print(f"ANSWER_PATH: {self.config['ANSWER_PATH']}")
569
+ self.console.print(f"COMPLETION_PATH: {self.config['COMPLETION_PATH']}")
570
+ self.console.print(f"BASE_URL: {self.config['BASE_URL']}")
571
+ self.console.print(f"MODEL: {self.config['MODEL']}")
572
+ self.console.print(f"SHELL_NAME: {self.config['SHELL_NAME']}")
573
+ self.console.print(f"OS_NAME: {self.config['OS_NAME']}")
574
+ self.console.print(f"STREAM: {self.config['STREAM']}")
575
+ self.console.print(f"TEMPERATURE: {self.config['TEMPERATURE']}")
576
+ self.console.print(f"TOP_P: {self.config['TOP_P']}")
577
+ self.console.print(f"MAX_TOKENS: {self.config['MAX_TOKENS']}")
493
578
  if not self.config.get("API_KEY"):
494
579
  self.console.print(
495
580
  "[yellow]API key not set. Please set in ~/.config/yaicli/config.ini or AI_API_KEY env[/]"
@@ -519,6 +604,11 @@ def main(
519
604
  template: Annotated[bool, typer.Option("--template", help="Show the config template.")] = False,
520
605
  ):
521
606
  """yaicli - Your AI interface in cli."""
607
+ # Check for stdin input (from pipe or redirect)
608
+ if not sys.stdin.isatty():
609
+ stdin_content = sys.stdin.read()
610
+ prompt = f"{stdin_content}\n\n{prompt}"
611
+
522
612
  if prompt == "":
523
613
  typer.echo("Empty prompt, ignored")
524
614
  return
@@ -1,7 +0,0 @@
1
- pyproject.toml,sha256=NQ2sC5Y_yR0RhPagRF8RaLK5geBVmvMxphi_PmlTw2w,1452
2
- yaicli.py,sha256=Vmawy7vedI8DXkTFARhi938UyzFeosQ0hoTrBaiERtA,21575
3
- yaicli-0.0.15.dist-info/METADATA,sha256=6YEwUIKK4eUS4EzCvRxBdx5nx0Euj2IBq73Z9_agtok,29412
4
- yaicli-0.0.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
- yaicli-0.0.15.dist-info/entry_points.txt,sha256=gdduQwAuu_LeDqnDU81Fv3NPmD2tRQ1FffvolIP3S1Q,34
6
- yaicli-0.0.15.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
7
- yaicli-0.0.15.dist-info/RECORD,,