ai-toolkit-cli 0.1.0__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.
- ai_toolkit_cli-0.1.0/PKG-INFO +7 -0
- ai_toolkit_cli-0.1.0/README.md +52 -0
- ai_toolkit_cli-0.1.0/ai_toolkit/commands/chat.py +49 -0
- ai_toolkit_cli-0.1.0/ai_toolkit/commands/codegen.py +64 -0
- ai_toolkit_cli-0.1.0/ai_toolkit/commands/summarize.py +65 -0
- ai_toolkit_cli-0.1.0/ai_toolkit/core/client.py +22 -0
- ai_toolkit_cli-0.1.0/ai_toolkit/main.py +54 -0
- ai_toolkit_cli-0.1.0/ai_toolkit_cli.egg-info/PKG-INFO +7 -0
- ai_toolkit_cli-0.1.0/ai_toolkit_cli.egg-info/SOURCES.txt +13 -0
- ai_toolkit_cli-0.1.0/ai_toolkit_cli.egg-info/dependency_links.txt +1 -0
- ai_toolkit_cli-0.1.0/ai_toolkit_cli.egg-info/entry_points.txt +2 -0
- ai_toolkit_cli-0.1.0/ai_toolkit_cli.egg-info/requires.txt +4 -0
- ai_toolkit_cli-0.1.0/ai_toolkit_cli.egg-info/top_level.txt +1 -0
- ai_toolkit_cli-0.1.0/pyproject.toml +11 -0
- ai_toolkit_cli-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ai-toolkit
|
|
2
|
+
|
|
3
|
+
`ai-toolkit` is a small Typer-based command-line app for AI-assisted terminal workflows.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
The CLI is organized into three command groups:
|
|
8
|
+
|
|
9
|
+
- `chat`
|
|
10
|
+
- `code`
|
|
11
|
+
- `summarize`
|
|
12
|
+
|
|
13
|
+
The top-level entry point is `ai`.
|
|
14
|
+
|
|
15
|
+
## Project Layout
|
|
16
|
+
|
|
17
|
+
- `main.py` - Typer application and command registration
|
|
18
|
+
- `commands/` - command group modules
|
|
19
|
+
- `core/` - client and configuration helpers
|
|
20
|
+
- `utils/` - shared formatting helpers
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
The project currently depends on:
|
|
25
|
+
|
|
26
|
+
- `typer`
|
|
27
|
+
- `rich`
|
|
28
|
+
- `openai`
|
|
29
|
+
- `python-dotenv`
|
|
30
|
+
|
|
31
|
+
## Setup
|
|
32
|
+
|
|
33
|
+
1. Create and activate a Python 3.12 virtual environment.
|
|
34
|
+
2. Install the project dependencies.
|
|
35
|
+
3. Run the CLI help to verify the app loads.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m venv .venv
|
|
41
|
+
.venv\Scripts\activate
|
|
42
|
+
pip install -e .
|
|
43
|
+
ai --help
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Environment
|
|
47
|
+
|
|
48
|
+
Store local configuration in a `.env` file. Keep API keys and machine-specific values out of version control.
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
The repository is still in an early stage, so command modules and supporting helpers are intentionally lightweight. Add usage examples here as the CLI grows.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import time
|
|
3
|
+
import threading
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from ai_toolkit.core.client import ask
|
|
7
|
+
|
|
8
|
+
os.system("") # enable ANSI on Windows
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
|
|
12
|
+
SPINNER_FRAMES = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]
|
|
13
|
+
|
|
14
|
+
def spinning(stop_event):
|
|
15
|
+
i = 0
|
|
16
|
+
while not stop_event.is_set():
|
|
17
|
+
frame = SPINNER_FRAMES[i % len(SPINNER_FRAMES)]
|
|
18
|
+
print(f"\r\033[96m {frame} Thinking...\033[0m", end="", flush=True)
|
|
19
|
+
time.sleep(0.08)
|
|
20
|
+
i += 1
|
|
21
|
+
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
22
|
+
|
|
23
|
+
def typewriter(text: str):
|
|
24
|
+
print("\n\033[96m ╔═ AI ════════════════════════════╗\033[0m")
|
|
25
|
+
print(" \033[97m", end="", flush=True)
|
|
26
|
+
for char in text:
|
|
27
|
+
print(char, end="", flush=True)
|
|
28
|
+
time.sleep(0.008)
|
|
29
|
+
print("\n\033[96m ╚═════════════════════════════════╝\033[0m\n")
|
|
30
|
+
|
|
31
|
+
@app.command()
|
|
32
|
+
def ask_cmd(
|
|
33
|
+
question: str = typer.Argument(..., help="Question to ask the AI"),
|
|
34
|
+
model: str = typer.Option("minimax/minimax-m2.5:free", "--model", "-m"),
|
|
35
|
+
):
|
|
36
|
+
"""Ask the AI a question."""
|
|
37
|
+
response_holder = {}
|
|
38
|
+
stop_event = threading.Event()
|
|
39
|
+
|
|
40
|
+
def fetch():
|
|
41
|
+
response_holder["result"] = ask(question, model=model)
|
|
42
|
+
stop_event.set()
|
|
43
|
+
|
|
44
|
+
thread = threading.Thread(target=fetch)
|
|
45
|
+
thread.start()
|
|
46
|
+
spinning(stop_event)
|
|
47
|
+
thread.join()
|
|
48
|
+
|
|
49
|
+
typewriter(response_holder["result"])
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import time
|
|
3
|
+
import threading
|
|
4
|
+
from rich.syntax import Syntax
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from ai_toolkit.core.client import ask
|
|
7
|
+
|
|
8
|
+
app = typer.Typer()
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
SPINNER_FRAMES = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def spinning(stop_event):
|
|
15
|
+
i = 0
|
|
16
|
+
while not stop_event.is_set():
|
|
17
|
+
frame = SPINNER_FRAMES[i % len(SPINNER_FRAMES)]
|
|
18
|
+
print(f"\r\033[96m {frame} Generating code...\033[0m", end="", flush=True)
|
|
19
|
+
time.sleep(0.08)
|
|
20
|
+
i += 1
|
|
21
|
+
print("\r" + " " * 40 + "\r", end="", flush=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command(context_settings={"allow_extra_args": True, "ignore_unknown_options": True})
|
|
25
|
+
def generate(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
lang: str = typer.Option("python", "--lang", "-l", help="Programming language"),
|
|
28
|
+
model: str = typer.Option("minimax/minimax-m2.5:free", "--model", "-m", help="Model to use"),
|
|
29
|
+
):
|
|
30
|
+
"""Generate code from a description."""
|
|
31
|
+
task = " ".join(ctx.args).strip()
|
|
32
|
+
|
|
33
|
+
if not task:
|
|
34
|
+
print("\n\033[38;5;213m ✦ Usage:\033[0m \033[38;5;240mai code generate <task> --lang python\033[0m\n")
|
|
35
|
+
raise typer.Exit()
|
|
36
|
+
|
|
37
|
+
prompt = f"Write {lang} code to: {task}. Return only the code, no explanation."
|
|
38
|
+
|
|
39
|
+
response_holder = {}
|
|
40
|
+
stop_event = threading.Event()
|
|
41
|
+
|
|
42
|
+
def fetch():
|
|
43
|
+
try:
|
|
44
|
+
response_holder["result"] = ask(prompt, model=model)
|
|
45
|
+
except Exception as e:
|
|
46
|
+
response_holder["result"] = f"# Error: {e}"
|
|
47
|
+
stop_event.set()
|
|
48
|
+
|
|
49
|
+
thread = threading.Thread(target=fetch)
|
|
50
|
+
thread.start()
|
|
51
|
+
spinning(stop_event)
|
|
52
|
+
thread.join()
|
|
53
|
+
|
|
54
|
+
code = response_holder.get("result", "# No response.")
|
|
55
|
+
|
|
56
|
+
# strip markdown fences if model returns them
|
|
57
|
+
if code.startswith("```"):
|
|
58
|
+
lines = code.split("\n")
|
|
59
|
+
code = "\n".join(lines[1:-1] if lines[-1].strip() == "```" else lines[1:])
|
|
60
|
+
|
|
61
|
+
print()
|
|
62
|
+
syntax = Syntax(code, lang, theme="monokai", line_numbers=True)
|
|
63
|
+
console.print(syntax)
|
|
64
|
+
print()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import threading
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from ai_toolkit.core.client import ask
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
os.system("") # enable ANSI on Windows
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
|
|
12
|
+
SPINNER_FRAMES = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def spinning(stop_event):
|
|
16
|
+
i = 0
|
|
17
|
+
while not stop_event.is_set():
|
|
18
|
+
frame = SPINNER_FRAMES[i % len(SPINNER_FRAMES)]
|
|
19
|
+
print(f"\r\033[96m {frame} Summarizing...\033[0m", end="", flush=True)
|
|
20
|
+
time.sleep(0.08)
|
|
21
|
+
i += 1
|
|
22
|
+
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def typewriter(text: str):
|
|
26
|
+
print("\n\033[96m ╔═ AI ════════════════════════════╗\033[0m")
|
|
27
|
+
print(" \033[97m", end="", flush=True)
|
|
28
|
+
for char in text:
|
|
29
|
+
print(char, end="", flush=True)
|
|
30
|
+
time.sleep(0.008)
|
|
31
|
+
print("\n\033[96m ╚═════════════════════════════════╝\033[0m\n")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@app.command()
|
|
35
|
+
def file(path: Path = typer.Argument(..., help="File to summarize")):
|
|
36
|
+
"""Summarize a text file."""
|
|
37
|
+
content = path.read_text()
|
|
38
|
+
|
|
39
|
+
stop_event = threading.Event()
|
|
40
|
+
spinner_thread = threading.Thread(target=spinning, args=(stop_event,))
|
|
41
|
+
spinner_thread.start()
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
summary = ask(f"Summarize this:\n\n{content}")
|
|
45
|
+
finally:
|
|
46
|
+
stop_event.set()
|
|
47
|
+
spinner_thread.join()
|
|
48
|
+
|
|
49
|
+
typewriter(summary)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@app.command()
|
|
53
|
+
def text(input: str = typer.Argument(...)):
|
|
54
|
+
"""Summarize a string of text."""
|
|
55
|
+
stop_event = threading.Event()
|
|
56
|
+
spinner_thread = threading.Thread(target=spinning, args=(stop_event,))
|
|
57
|
+
spinner_thread.start()
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
result = ask(f"Summarize: {input}")
|
|
61
|
+
finally:
|
|
62
|
+
stop_event.set()
|
|
63
|
+
spinner_thread.join()
|
|
64
|
+
|
|
65
|
+
typewriter(result)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from openai import OpenAI
|
|
3
|
+
from dotenv import load_dotenv
|
|
4
|
+
|
|
5
|
+
load_dotenv()
|
|
6
|
+
|
|
7
|
+
def get_client():
|
|
8
|
+
return OpenAI(
|
|
9
|
+
api_key=os.environ["OPENROUTER_API_KEY"],
|
|
10
|
+
base_url="https://openrouter.ai/api/v1",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def ask(prompt: str, system: str = "You are a helpful assistant.", model: str = "minimax/minimax-m2.5:free") -> str:
|
|
14
|
+
client = get_client()
|
|
15
|
+
response = client.chat.completions.create(
|
|
16
|
+
model=model,
|
|
17
|
+
messages=[
|
|
18
|
+
{"role": "system", "content": system},
|
|
19
|
+
{"role": "user", "content": prompt}
|
|
20
|
+
]
|
|
21
|
+
)
|
|
22
|
+
return response.choices[0].message.content
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import time
|
|
3
|
+
import os
|
|
4
|
+
from ai_toolkit.commands import chat, summarize, codegen
|
|
5
|
+
|
|
6
|
+
os.system("") # enable ANSI on Windows
|
|
7
|
+
|
|
8
|
+
def show_banner():
|
|
9
|
+
banner = """
|
|
10
|
+
\033[96m ╔══════════════════════════════════════════╗
|
|
11
|
+
║ ║
|
|
12
|
+
║ \033[94m █████╗ ██╗ ████████╗ ██████╗ \033[96m ║
|
|
13
|
+
║ \033[94m██╔══██╗██║ ██║ ██╔═══██╗\033[96m ║
|
|
14
|
+
║ \033[94m███████║██║ ██║ ██║ ██║\033[96m ║
|
|
15
|
+
║ \033[94m██╔══██║██║ ██║ ██║ ██║\033[96m ║
|
|
16
|
+
║ \033[94m██║ ██║███████╗ ██║ ╚██████╔╝\033[96m ║
|
|
17
|
+
║ \033[94m╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ \033[96m ║
|
|
18
|
+
║ ║
|
|
19
|
+
║ \033[97m🤖 AI Toolkit CLI v1.0.0 \033[96m ║
|
|
20
|
+
║ \033[92m⚡ Powered by OpenRouter \033[96m ║
|
|
21
|
+
║ ║
|
|
22
|
+
╚══════════════════════════════════════════╝\033[0m"""
|
|
23
|
+
|
|
24
|
+
for line in banner.split("\n"):
|
|
25
|
+
print(line)
|
|
26
|
+
time.sleep(0.04)
|
|
27
|
+
|
|
28
|
+
tagline = "\n → Type ai --help to get started\n"
|
|
29
|
+
print("\033[93m", end="", flush=True)
|
|
30
|
+
for char in tagline:
|
|
31
|
+
print(char, end="", flush=True)
|
|
32
|
+
time.sleep(0.03)
|
|
33
|
+
print("\033[0m")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
app = typer.Typer(
|
|
37
|
+
name="ai",
|
|
38
|
+
help="🤖 AI Toolkit CLI — powered by OpenRouter",
|
|
39
|
+
invoke_without_command=True,
|
|
40
|
+
no_args_is_help=False, # ← important: don't show help when no args
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
@app.callback()
|
|
44
|
+
def main(ctx: typer.Context = typer.Option(None, hidden=True, is_eager=True)):
|
|
45
|
+
if ctx.invoked_subcommand is None:
|
|
46
|
+
show_banner()
|
|
47
|
+
raise typer.Exit()
|
|
48
|
+
|
|
49
|
+
app.add_typer(chat.app, name="chat")
|
|
50
|
+
app.add_typer(summarize.app, name="summarize")
|
|
51
|
+
app.add_typer(codegen.app, name="code")
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
app()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
ai_toolkit/main.py
|
|
4
|
+
ai_toolkit/commands/chat.py
|
|
5
|
+
ai_toolkit/commands/codegen.py
|
|
6
|
+
ai_toolkit/commands/summarize.py
|
|
7
|
+
ai_toolkit/core/client.py
|
|
8
|
+
ai_toolkit_cli.egg-info/PKG-INFO
|
|
9
|
+
ai_toolkit_cli.egg-info/SOURCES.txt
|
|
10
|
+
ai_toolkit_cli.egg-info/dependency_links.txt
|
|
11
|
+
ai_toolkit_cli.egg-info/entry_points.txt
|
|
12
|
+
ai_toolkit_cli.egg-info/requires.txt
|
|
13
|
+
ai_toolkit_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ai_toolkit
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ai-toolkit-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
dependencies = ["typer", "rich", "openai", "python-dotenv"]
|
|
5
|
+
|
|
6
|
+
[project.scripts]
|
|
7
|
+
ai = "ai_toolkit.main:app"
|
|
8
|
+
|
|
9
|
+
[build-system]
|
|
10
|
+
requires = ["setuptools>=68", "wheel"]
|
|
11
|
+
build-backend = "setuptools.build_meta"
|