lanthei-cli 0.1.0__tar.gz → 0.2.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.
- {lanthei_cli-0.1.0 → lanthei_cli-0.2.0}/PKG-INFO +2 -1
- lanthei_cli-0.2.0/README.md +28 -0
- lanthei_cli-0.2.0/lanthei/cli.py +154 -0
- lanthei_cli-0.2.0/lanthei/cli.pypip +0 -0
- lanthei_cli-0.2.0/lanthei_pkg/pyproject.toml +0 -0
- {lanthei_cli-0.1.0 → lanthei_cli-0.2.0}/pyproject.toml +2 -2
- lanthei_cli-0.1.0/lanthei/cli.py +0 -38
- {lanthei_cli-0.1.0 → lanthei_cli-0.2.0}/lanthei/__init__.py +0 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Lanthei CLI
|
|
2
|
+
|
|
3
|
+
A simple command-line tool that lets you chat with Google's Gemini AI directly from your terminal — using your own free API key.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
pip install lanthei-cli
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Ask a question directly:
|
|
12
|
+
|
|
13
|
+
lanthei "explain what a python list comprehension is"
|
|
14
|
+
|
|
15
|
+
Or run it with no arguments for an interactive prompt:
|
|
16
|
+
|
|
17
|
+
lanthei
|
|
18
|
+
|
|
19
|
+
The first time you run it, you'll be asked to paste your Gemini API key (get one free at https://aistudio.google.com). It's saved locally at `~/.lanthei_key.txt` and only used on your machine — never sent anywhere except directly to Google's API.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Python 3.9+
|
|
24
|
+
- A free Gemini API key from Google AI Studio
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
MIT
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# This script provides a command-line interface (CLI) to interact with the Google Gemini AI
|
|
2
|
+
# model through the OpenAI API facade. It allows users to:
|
|
3
|
+
# - Chat with Gemini (default command or `chat`)
|
|
4
|
+
# - Explain the contents of a specified code file (`explain`)
|
|
5
|
+
# - Fix or modify a specified code file based on an instruction (`fix`), with diff review and Git commit integration.
|
|
6
|
+
# It stores the Gemini API key locally for convenience.
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import argparse
|
|
11
|
+
import difflib
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from openai import OpenAI
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.panel import Panel
|
|
16
|
+
from rich.syntax import Syntax
|
|
17
|
+
|
|
18
|
+
console = Console()
|
|
19
|
+
CONFIG_PATH = Path.home() / ".lanthei_key.txt"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_api_key():
|
|
23
|
+
if CONFIG_PATH.exists():
|
|
24
|
+
return CONFIG_PATH.read_text().strip()
|
|
25
|
+
key = input("Enter your Gemini API key (get one free at aistudio.google.com): ").strip()
|
|
26
|
+
CONFIG_PATH.write_text(key)
|
|
27
|
+
console.print(f"[green]Saved.[/green] (stored locally at {CONFIG_PATH})")
|
|
28
|
+
return key
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_client():
|
|
32
|
+
api_key = get_api_key()
|
|
33
|
+
return OpenAI(
|
|
34
|
+
api_key=api_key,
|
|
35
|
+
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ask_gemini(client, prompt, model="gemini-2.5-flash"):
|
|
40
|
+
response = client.chat.completions.create(
|
|
41
|
+
model=model,
|
|
42
|
+
messages=[{"role": "user", "content": prompt}],
|
|
43
|
+
)
|
|
44
|
+
return response.choices[0].message.content
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def cmd_chat(args):
|
|
48
|
+
client = get_client()
|
|
49
|
+
question = " ".join(args.question) if args.question else input("Ask me anything: ")
|
|
50
|
+
try:
|
|
51
|
+
console.print(ask_gemini(client, question))
|
|
52
|
+
except Exception as e:
|
|
53
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def cmd_explain(args):
|
|
57
|
+
filepath = Path(args.file)
|
|
58
|
+
if not filepath.exists():
|
|
59
|
+
console.print(f"[red]File not found: {filepath}[/red]")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
code = filepath.read_text()
|
|
63
|
+
client = get_client()
|
|
64
|
+
prompt = f"Explain what this code does, in plain language:\n\n{code}"
|
|
65
|
+
try:
|
|
66
|
+
console.print(ask_gemini(client, prompt))
|
|
67
|
+
except Exception as e:
|
|
68
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cmd_fix(args):
|
|
72
|
+
filepath = Path(args.file)
|
|
73
|
+
if not filepath.exists():
|
|
74
|
+
console.print(f"[red]File not found: {filepath}[/red]")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
original_code = filepath.read_text()
|
|
78
|
+
instruction = " ".join(args.instruction)
|
|
79
|
+
client = get_client()
|
|
80
|
+
|
|
81
|
+
prompt = (
|
|
82
|
+
f"Here is a Python file:\n\n{original_code}\n\n"
|
|
83
|
+
f"Task: {instruction}\n\n"
|
|
84
|
+
"Return ONLY the complete corrected file content, with no explanation, "
|
|
85
|
+
"no markdown code fences, just the raw file contents."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
console.print("[yellow]Asking Gemini for changes...[/yellow]")
|
|
89
|
+
try:
|
|
90
|
+
new_code = ask_gemini(client, prompt)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
new_code = new_code.strip()
|
|
96
|
+
if new_code.startswith("```"):
|
|
97
|
+
lines = new_code.split("\n")
|
|
98
|
+
new_code = "\n".join(lines[1:-1]) if lines[-1].strip() == "```" else "\n".join(lines[1:])
|
|
99
|
+
|
|
100
|
+
diff = list(difflib.unified_diff(
|
|
101
|
+
original_code.splitlines(keepends=True),
|
|
102
|
+
new_code.splitlines(keepends=True),
|
|
103
|
+
fromfile="before",
|
|
104
|
+
tofile="after",
|
|
105
|
+
))
|
|
106
|
+
|
|
107
|
+
if not diff:
|
|
108
|
+
console.print("[yellow]No changes suggested.[/yellow]")
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
diff_text = "".join(diff)
|
|
112
|
+
console.print(Panel(Syntax(diff_text, "diff", theme="ansi_dark"), title="Proposed Changes"))
|
|
113
|
+
|
|
114
|
+
console.print("\n[bold green][A][/bold green] Apply & Commit [bold red][R][/bold red] Reject")
|
|
115
|
+
choice = input("Choose: ").strip().lower()
|
|
116
|
+
|
|
117
|
+
if choice == "a":
|
|
118
|
+
filepath.write_text(new_code)
|
|
119
|
+
os.system(f'git add "{filepath}"')
|
|
120
|
+
os.system(f'git commit -m "AI edit: {instruction}"')
|
|
121
|
+
console.print(f"[green]Applied and committed changes to {filepath}[/green]")
|
|
122
|
+
else:
|
|
123
|
+
console.print("[yellow]Rejected. No changes made.[/yellow]")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main():
|
|
127
|
+
known_commands = {"explain", "fix", "chat"}
|
|
128
|
+
|
|
129
|
+
if len(sys.argv) > 1 and sys.argv[1] in known_commands:
|
|
130
|
+
parser = argparse.ArgumentParser(prog="lanthei")
|
|
131
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
132
|
+
|
|
133
|
+
explain_parser = subparsers.add_parser("explain")
|
|
134
|
+
explain_parser.add_argument("file")
|
|
135
|
+
explain_parser.set_defaults(func=cmd_explain)
|
|
136
|
+
|
|
137
|
+
fix_parser = subparsers.add_parser("fix")
|
|
138
|
+
fix_parser.add_argument("file")
|
|
139
|
+
fix_parser.add_argument("instruction", nargs="+")
|
|
140
|
+
fix_parser.set_defaults(func=cmd_fix)
|
|
141
|
+
|
|
142
|
+
chat_parser = subparsers.add_parser("chat")
|
|
143
|
+
chat_parser.add_argument("question", nargs="*")
|
|
144
|
+
chat_parser.set_defaults(func=cmd_chat)
|
|
145
|
+
|
|
146
|
+
args = parser.parse_args()
|
|
147
|
+
args.func(args)
|
|
148
|
+
else:
|
|
149
|
+
chat_args = argparse.Namespace(question=sys.argv[1:])
|
|
150
|
+
cmd_chat(chat_args)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lanthei-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "A simple CLI that chats with Gemini using your own API key"
|
|
5
5
|
requires-python = ">=3.9"
|
|
6
|
-
dependencies = ["openai"]
|
|
6
|
+
dependencies = ["openai", "rich"]
|
|
7
7
|
|
|
8
8
|
[project.scripts]
|
|
9
9
|
lanthei = "lanthei.cli:main"
|
lanthei_cli-0.1.0/lanthei/cli.py
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from openai import OpenAI
|
|
5
|
-
|
|
6
|
-
CONFIG_PATH = Path.home() / ".lanthei_key.txt"
|
|
7
|
-
|
|
8
|
-
def get_api_key():
|
|
9
|
-
if CONFIG_PATH.exists():
|
|
10
|
-
return CONFIG_PATH.read_text().strip()
|
|
11
|
-
key = input("Enter your Gemini API key (get one free at aistudio.google.com): ").strip()
|
|
12
|
-
CONFIG_PATH.write_text(key)
|
|
13
|
-
print(f"Saved. (stored locally at {CONFIG_PATH})")
|
|
14
|
-
return key
|
|
15
|
-
|
|
16
|
-
def main():
|
|
17
|
-
api_key = get_api_key()
|
|
18
|
-
client = OpenAI(
|
|
19
|
-
api_key=api_key,
|
|
20
|
-
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
if len(sys.argv) > 1:
|
|
24
|
-
question = " ".join(sys.argv[1:])
|
|
25
|
-
else:
|
|
26
|
-
question = input("Ask me anything: ")
|
|
27
|
-
|
|
28
|
-
try:
|
|
29
|
-
response = client.chat.completions.create(
|
|
30
|
-
model="gemini-2.5-flash",
|
|
31
|
-
messages=[{"role": "user", "content": question}],
|
|
32
|
-
)
|
|
33
|
-
print(response.choices[0].message.content)
|
|
34
|
-
except Exception as e:
|
|
35
|
-
print(f"Error: {e}")
|
|
36
|
-
|
|
37
|
-
if __name__ == "__main__":
|
|
38
|
-
main()
|
|
File without changes
|