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.
|
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
|
-

|
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
|
-
- **
|
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
|
-

|
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
|
-
- **
|
375
|
+
- **httpx**: Handles API requests
|
354
376
|
- **jmespath**: Parses JSON responses
|
355
377
|
|
356
378
|
## Contributing
|
@@ -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=
|
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
|
-
|
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
|