llamapass 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.
- llamapass-0.1.0/.gitignore +15 -0
- llamapass-0.1.0/PKG-INFO +33 -0
- llamapass-0.1.0/README.md +24 -0
- llamapass-0.1.0/llamapass_cli/__init__.py +1 -0
- llamapass-0.1.0/llamapass_cli/__main__.py +3 -0
- llamapass-0.1.0/llamapass_cli/cli.py +100 -0
- llamapass-0.1.0/llamapass_cli/client.py +99 -0
- llamapass-0.1.0/llamapass_cli/config.py +33 -0
- llamapass-0.1.0/pyproject.toml +20 -0
llamapass-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llamapass
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI client for LlamaPass - Ollama gateway with authentication
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Requires-Dist: httpx>=0.27
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# LlamaPass CLI
|
|
11
|
+
|
|
12
|
+
CLI client for [LlamaPass](https://llamapass.org) - an Ollama gateway with authentication.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install llamapass
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
llamapass config set-url https://llamapass.org
|
|
24
|
+
llamapass config set-key oah_your_api_key
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
llamapass run gemma3 # interactive chat
|
|
31
|
+
llamapass list # list available models
|
|
32
|
+
llamapass config show # show current config
|
|
33
|
+
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# LlamaPass CLI
|
|
2
|
+
|
|
3
|
+
CLI client for [LlamaPass](https://llamapass.org) - an Ollama gateway with authentication.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install llamapass
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
llamapass config set-url https://llamapass.org
|
|
15
|
+
llamapass config set-key oah_your_api_key
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
llamapass run gemma3 # interactive chat
|
|
22
|
+
llamapass list # list available models
|
|
23
|
+
llamapass config show # show current config
|
|
24
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from llamapass_cli import __version__
|
|
5
|
+
from llamapass_cli.client import chat_stream, list_models
|
|
6
|
+
from llamapass_cli.config import get, load, set_value
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def cmd_run(args):
|
|
10
|
+
model = args.model
|
|
11
|
+
messages = []
|
|
12
|
+
print(f"LlamaPass - {model} (type /bye to quit)\n")
|
|
13
|
+
|
|
14
|
+
while True:
|
|
15
|
+
try:
|
|
16
|
+
user_input = input(">>> ")
|
|
17
|
+
except (KeyboardInterrupt, EOFError):
|
|
18
|
+
print("\nBye!")
|
|
19
|
+
break
|
|
20
|
+
|
|
21
|
+
if user_input.strip().lower() in ("/bye", "/exit", "/quit"):
|
|
22
|
+
print("Bye!")
|
|
23
|
+
break
|
|
24
|
+
|
|
25
|
+
if not user_input.strip():
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
messages.append({"role": "user", "content": user_input})
|
|
29
|
+
response = chat_stream(model, messages)
|
|
30
|
+
if response:
|
|
31
|
+
messages.append({"role": "assistant", "content": response})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cmd_list(args):
|
|
35
|
+
list_models()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_config(args):
|
|
39
|
+
action = args.action
|
|
40
|
+
|
|
41
|
+
if action == "show":
|
|
42
|
+
cfg = load()
|
|
43
|
+
print(f" url: {cfg['url']}")
|
|
44
|
+
print(f" api_key: {cfg['api_key'][:10]}..." if cfg["api_key"] else " api_key: (not set)")
|
|
45
|
+
|
|
46
|
+
elif action == "set-url":
|
|
47
|
+
if not args.value:
|
|
48
|
+
print("Usage: llamapass config set-url <url>")
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
set_value("url", args.value)
|
|
51
|
+
print(f"URL set to: {args.value}")
|
|
52
|
+
|
|
53
|
+
elif action == "set-key":
|
|
54
|
+
if not args.value:
|
|
55
|
+
print("Usage: llamapass config set-key <key>")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
set_value("api_key", args.value)
|
|
58
|
+
print(f"API key saved.")
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
print(f"Unknown action: {action}")
|
|
62
|
+
print("Available: show, set-url, set-key")
|
|
63
|
+
sys.exit(1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def main():
|
|
67
|
+
parser = argparse.ArgumentParser(
|
|
68
|
+
prog="llamapass",
|
|
69
|
+
description="CLI client for LlamaPass",
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
72
|
+
|
|
73
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
74
|
+
|
|
75
|
+
# run
|
|
76
|
+
run_parser = subparsers.add_parser("run", help="Chat with a model")
|
|
77
|
+
run_parser.add_argument("model", help="Model name (e.g. gemma3)")
|
|
78
|
+
|
|
79
|
+
# list
|
|
80
|
+
subparsers.add_parser("list", help="List available models")
|
|
81
|
+
subparsers.add_parser("ls", help="List available models")
|
|
82
|
+
|
|
83
|
+
# config
|
|
84
|
+
config_parser = subparsers.add_parser("config", help="Manage configuration")
|
|
85
|
+
config_parser.add_argument("action", help="show | set-url | set-key")
|
|
86
|
+
config_parser.add_argument("value", nargs="?", help="Value to set")
|
|
87
|
+
|
|
88
|
+
args = parser.parse_args()
|
|
89
|
+
|
|
90
|
+
if args.command is None:
|
|
91
|
+
parser.print_help()
|
|
92
|
+
sys.exit(0)
|
|
93
|
+
|
|
94
|
+
commands = {
|
|
95
|
+
"run": cmd_run,
|
|
96
|
+
"list": cmd_list,
|
|
97
|
+
"ls": cmd_list,
|
|
98
|
+
"config": cmd_config,
|
|
99
|
+
}
|
|
100
|
+
commands[args.command](args)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from llamapass_cli.config import load
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _get_client():
|
|
10
|
+
cfg = load()
|
|
11
|
+
url = cfg["url"].rstrip("/")
|
|
12
|
+
api_key = cfg.get("api_key", "")
|
|
13
|
+
if not api_key:
|
|
14
|
+
print("Error: no API key configured. Run: llamapass config set-key <key>")
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
headers = {
|
|
17
|
+
"Authorization": f"Bearer {api_key}",
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
}
|
|
20
|
+
return url, headers
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def list_models():
|
|
24
|
+
url, headers = _get_client()
|
|
25
|
+
try:
|
|
26
|
+
resp = httpx.get(f"{url}/ollama/api/tags", headers=headers, timeout=30)
|
|
27
|
+
resp.raise_for_status()
|
|
28
|
+
data = resp.json()
|
|
29
|
+
models = data.get("models", [])
|
|
30
|
+
if not models:
|
|
31
|
+
print("No models available.")
|
|
32
|
+
return
|
|
33
|
+
print(f"{'NAME':<40} {'SIZE':<12} {'MODIFIED'}")
|
|
34
|
+
for m in models:
|
|
35
|
+
name = m.get("name", "?")
|
|
36
|
+
size_gb = m.get("size", 0) / (1024**3)
|
|
37
|
+
modified = m.get("modified_at", "?")[:19]
|
|
38
|
+
print(f"{name:<40} {size_gb:<12.1f}GB {modified}")
|
|
39
|
+
except httpx.ConnectError:
|
|
40
|
+
print(f"Error: cannot connect to {url}")
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
except httpx.HTTPStatusError as e:
|
|
43
|
+
_handle_http_error(e.response)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def chat_stream(model, messages):
|
|
47
|
+
url, headers = _get_client()
|
|
48
|
+
payload = {
|
|
49
|
+
"model": model,
|
|
50
|
+
"messages": messages,
|
|
51
|
+
"stream": True,
|
|
52
|
+
}
|
|
53
|
+
try:
|
|
54
|
+
with httpx.stream(
|
|
55
|
+
"POST",
|
|
56
|
+
f"{url}/ollama/api/chat",
|
|
57
|
+
json=payload,
|
|
58
|
+
headers=headers,
|
|
59
|
+
timeout=None,
|
|
60
|
+
) as resp:
|
|
61
|
+
if resp.status_code != 200:
|
|
62
|
+
_handle_http_error(resp)
|
|
63
|
+
return ""
|
|
64
|
+
full_response = []
|
|
65
|
+
for line in resp.iter_lines():
|
|
66
|
+
if not line:
|
|
67
|
+
continue
|
|
68
|
+
try:
|
|
69
|
+
data = json.loads(line)
|
|
70
|
+
content = data.get("message", {}).get("content", "")
|
|
71
|
+
if content:
|
|
72
|
+
print(content, end="", flush=True)
|
|
73
|
+
full_response.append(content)
|
|
74
|
+
if data.get("done"):
|
|
75
|
+
break
|
|
76
|
+
except json.JSONDecodeError:
|
|
77
|
+
continue
|
|
78
|
+
print()
|
|
79
|
+
return "".join(full_response)
|
|
80
|
+
except httpx.ConnectError:
|
|
81
|
+
print(f"\nError: cannot connect to {url}")
|
|
82
|
+
sys.exit(1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _handle_http_error(resp):
|
|
86
|
+
try:
|
|
87
|
+
data = resp.json()
|
|
88
|
+
error = data.get("error", resp.status_code)
|
|
89
|
+
except Exception:
|
|
90
|
+
error = resp.status_code
|
|
91
|
+
|
|
92
|
+
messages = {
|
|
93
|
+
401: "Invalid API key. Run: llamapass config set-key <key>",
|
|
94
|
+
403: f"Access denied: {error}",
|
|
95
|
+
429: "Rate limited. Try again later.",
|
|
96
|
+
502: "LlamaPass server cannot reach upstream Ollama.",
|
|
97
|
+
}
|
|
98
|
+
print(f"Error: {messages.get(resp.status_code, f'HTTP {resp.status_code}: {error}')}")
|
|
99
|
+
sys.exit(1)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
CONFIG_DIR = Path.home() / ".config" / "llamapass"
|
|
5
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
6
|
+
|
|
7
|
+
DEFAULTS = {
|
|
8
|
+
"url": "https://llamapass.org",
|
|
9
|
+
"api_key": "",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load():
|
|
14
|
+
if CONFIG_FILE.exists():
|
|
15
|
+
with open(CONFIG_FILE) as f:
|
|
16
|
+
return {**DEFAULTS, **json.load(f)}
|
|
17
|
+
return dict(DEFAULTS)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def save(cfg):
|
|
21
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
with open(CONFIG_FILE, "w") as f:
|
|
23
|
+
json.dump(cfg, f, indent=2)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get(key):
|
|
27
|
+
return load().get(key)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def set_value(key, value):
|
|
31
|
+
cfg = load()
|
|
32
|
+
cfg[key] = value
|
|
33
|
+
save(cfg)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "llamapass"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI client for LlamaPass - Ollama gateway with authentication"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.27",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.hatch.build.targets.wheel]
|
|
17
|
+
packages = ["llamapass_cli"]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
llamapass = "llamapass_cli.cli:main"
|