yaicli 0.0.15__tar.gz → 0.0.17__tar.gz

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.
@@ -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
@@ -35,6 +35,8 @@ Support regular and deep thinking models.
35
35
 
36
36
  - **Keyboard Shortcuts**:
37
37
  - Tab to switch between Chat and Execute modes
38
+ - `↑/↓` to navigate history
39
+ - `Ctrl+R` to search history
38
40
 
39
41
  - **History**:
40
42
  - Save and recall previous queries
@@ -118,6 +120,7 @@ Below are the available configuration options and override environment variables
118
120
  - **TEMPERATURE**: Temperature for response generation (default: 0.7), env: YAI_TEMPERATURE
119
121
  - **TOP_P**: Top-p sampling for response generation (default: 1.0), env: YAI_TOP_P
120
122
  - **MAX_TOKENS**: Maximum number of tokens for response generation (default: 1024), env: YAI_MAX_TOKENS
123
+ - **MAX_HISTORY**: Max history size, default: 500, env: YAI_MAX_HISTORY
121
124
 
122
125
  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.
123
126
 
@@ -175,7 +178,7 @@ If you not sure how to config `COMPLETION_PATH` and `ANSWER_PATH`, here is a gui
175
178
  You can find the list of code theme here: https://pygments.org/styles/
176
179
 
177
180
  Default: monokia
178
- ![alt text](artwork/monokia.png)
181
+ ![monikia](artwork/monokia.png)
179
182
 
180
183
  ## Usage
181
184
 
@@ -252,6 +255,25 @@ In Execute mode:
252
255
  3. Review the command
253
256
  4. Confirm to execute or reject
254
257
 
258
+ ### Keyboard Shortcuts
259
+ - `Tab`: Switch between Chat and Execute modes
260
+ - `Ctrl+C`: Exit
261
+ - `Ctrl+R`: Search history
262
+ - `↑/↓`: Navigate history
263
+
264
+ ### Stdin
265
+ You can also pipe input to YAICLI:
266
+ ```bash
267
+ echo "What is the capital of France?" | ai
268
+ ```
269
+
270
+ ```bash
271
+ cat demo.py | ai "How to use this tool?"
272
+ ```
273
+
274
+ ### History
275
+ Support max history size. Set MAX_HISTORY in config file. Default is 500.
276
+
255
277
  ## Examples
256
278
 
257
279
  ### Have a Chat
@@ -350,7 +372,7 @@ YAICLI is built using several Python libraries:
350
372
  - **Typer**: Provides the command-line interface
351
373
  - **Rich**: Provides terminal content formatting and beautiful display
352
374
  - **prompt_toolkit**: Provides interactive command-line input experience
353
- - **requests**: Handles API requests
375
+ - **httpx**: Handles API requests
354
376
  - **jmespath**: Parses JSON responses
355
377
 
356
378
  ## Contributing
@@ -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"
@@ -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
File without changes
File without changes