slice-gateway 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.
- slice_gateway-0.1.0/LICENSE +21 -0
- slice_gateway-0.1.0/MANIFEST.in +25 -0
- slice_gateway-0.1.0/PKG-INFO +50 -0
- slice_gateway-0.1.0/PYPI.md +31 -0
- slice_gateway-0.1.0/pyproject.toml +39 -0
- slice_gateway-0.1.0/setup.cfg +4 -0
- slice_gateway-0.1.0/slice_cli.py +242 -0
- slice_gateway-0.1.0/slice_gateway.egg-info/PKG-INFO +50 -0
- slice_gateway-0.1.0/slice_gateway.egg-info/SOURCES.txt +11 -0
- slice_gateway-0.1.0/slice_gateway.egg-info/dependency_links.txt +1 -0
- slice_gateway-0.1.0/slice_gateway.egg-info/entry_points.txt +2 -0
- slice_gateway-0.1.0/slice_gateway.egg-info/requires.txt +2 -0
- slice_gateway-0.1.0/slice_gateway.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jishnu Janardanan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# The slice-gateway distribution is the CLI only. Keep the sdist to the CLI module,
|
|
2
|
+
# its PyPI readme, the license, and build metadata. Nothing from the server repo.
|
|
3
|
+
prune tests
|
|
4
|
+
prune app
|
|
5
|
+
prune infra
|
|
6
|
+
prune k8s
|
|
7
|
+
prune dashboard
|
|
8
|
+
prune desktop
|
|
9
|
+
prune website
|
|
10
|
+
prune migrations
|
|
11
|
+
prune demo
|
|
12
|
+
prune scripts
|
|
13
|
+
prune guardrails
|
|
14
|
+
prune docs
|
|
15
|
+
prune mcp_server
|
|
16
|
+
prune gateway
|
|
17
|
+
prune pipeline
|
|
18
|
+
prune rag_store
|
|
19
|
+
exclude README.md
|
|
20
|
+
exclude requirements.txt
|
|
21
|
+
exclude docker-compose.yml
|
|
22
|
+
exclude Dockerfile
|
|
23
|
+
exclude pytest.ini
|
|
24
|
+
global-exclude *.pyc
|
|
25
|
+
global-exclude .DS_Store
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slice-gateway
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line client for slice, the self-hosted LLM gateway.
|
|
5
|
+
Author: Jishnu Janardanan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sliceapp.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/jjk30/slice
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Utilities
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: typer
|
|
17
|
+
Requires-Dist: httpx
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# slice-gateway
|
|
21
|
+
|
|
22
|
+
Command-line client for slice, a self-hosted LLM gateway that routes, caches, and caps your AI spend.
|
|
23
|
+
|
|
24
|
+
This package installs the `slice` command. It logs you in through GitHub, then prints the environment lines that point your existing tools at a slice gateway. It has no server dependencies. It needs only typer and httpx, plus the standard library.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
1. Install the CLI:
|
|
29
|
+
|
|
30
|
+
pip install slice-gateway
|
|
31
|
+
|
|
32
|
+
2. Log in through GitHub:
|
|
33
|
+
|
|
34
|
+
slice login
|
|
35
|
+
|
|
36
|
+
3. Point a tool at slice. For Claude Code, set three variables:
|
|
37
|
+
|
|
38
|
+
export ANTHROPIC_BASE_URL=https://api.sliceapp.dev
|
|
39
|
+
export ANTHROPIC_API_KEY=sk-ant-api... # your own Anthropic key
|
|
40
|
+
export ANTHROPIC_AUTH_TOKEN=slk_live_... # your slice key, from slice login
|
|
41
|
+
|
|
42
|
+
`ANTHROPIC_AUTH_TOKEN` goes out as `Authorization: Bearer`, which is where slice reads its key. `ANTHROPIC_API_KEY` stays your own Anthropic key in `x-api-key`, and slice forwards it upstream.
|
|
43
|
+
|
|
44
|
+
Run `slice use anthropic`, `slice use openai`, `slice use curl`, or `slice use claude-code` to print the exact lines for your tool.
|
|
45
|
+
|
|
46
|
+
## The gateway
|
|
47
|
+
|
|
48
|
+
This is only the client. The slice gateway itself, the server that does the routing, caching, budgets, and dashboards, lives on GitHub at https://github.com/jjk30/slice.
|
|
49
|
+
|
|
50
|
+
Homepage: https://sliceapp.dev
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# slice-gateway
|
|
2
|
+
|
|
3
|
+
Command-line client for slice, a self-hosted LLM gateway that routes, caches, and caps your AI spend.
|
|
4
|
+
|
|
5
|
+
This package installs the `slice` command. It logs you in through GitHub, then prints the environment lines that point your existing tools at a slice gateway. It has no server dependencies. It needs only typer and httpx, plus the standard library.
|
|
6
|
+
|
|
7
|
+
## Quickstart
|
|
8
|
+
|
|
9
|
+
1. Install the CLI:
|
|
10
|
+
|
|
11
|
+
pip install slice-gateway
|
|
12
|
+
|
|
13
|
+
2. Log in through GitHub:
|
|
14
|
+
|
|
15
|
+
slice login
|
|
16
|
+
|
|
17
|
+
3. Point a tool at slice. For Claude Code, set three variables:
|
|
18
|
+
|
|
19
|
+
export ANTHROPIC_BASE_URL=https://api.sliceapp.dev
|
|
20
|
+
export ANTHROPIC_API_KEY=sk-ant-api... # your own Anthropic key
|
|
21
|
+
export ANTHROPIC_AUTH_TOKEN=slk_live_... # your slice key, from slice login
|
|
22
|
+
|
|
23
|
+
`ANTHROPIC_AUTH_TOKEN` goes out as `Authorization: Bearer`, which is where slice reads its key. `ANTHROPIC_API_KEY` stays your own Anthropic key in `x-api-key`, and slice forwards it upstream.
|
|
24
|
+
|
|
25
|
+
Run `slice use anthropic`, `slice use openai`, `slice use curl`, or `slice use claude-code` to print the exact lines for your tool.
|
|
26
|
+
|
|
27
|
+
## The gateway
|
|
28
|
+
|
|
29
|
+
This is only the client. The slice gateway itself, the server that does the routing, caching, budgets, and dashboards, lives on GitHub at https://github.com/jjk30/slice.
|
|
30
|
+
|
|
31
|
+
Homepage: https://sliceapp.dev
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Builds the `slice-gateway` distribution: the slice CLI only (slice login / init / use).
|
|
2
|
+
# The wheel ships a single top-level module, slice_cli.py, and depends on typer and httpx
|
|
3
|
+
# only. None of the server stack (fastapi, langgraph, redis, torch, ...) is imported or
|
|
4
|
+
# required. Inside the repo, `python -m app.cli` still works via the app/cli.py shim.
|
|
5
|
+
[build-system]
|
|
6
|
+
requires = ["setuptools>=77"]
|
|
7
|
+
build-backend = "setuptools.build_meta"
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "slice-gateway"
|
|
11
|
+
version = "0.1.0"
|
|
12
|
+
description = "Command-line client for slice, the self-hosted LLM gateway."
|
|
13
|
+
readme = "PYPI.md"
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
license = "MIT"
|
|
16
|
+
authors = [{ name = "Jishnu Janardanan" }]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"typer",
|
|
19
|
+
"httpx",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Environment :: Console",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Topic :: Utilities",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://sliceapp.dev"
|
|
30
|
+
Repository = "https://github.com/jjk30/slice"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
# `slice login`, `slice init`, `slice use <tool>`. See slice_cli.py.
|
|
34
|
+
slice = "slice_cli:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
# Ship only the CLI module. Setting py-modules disables package auto-discovery, so the
|
|
38
|
+
# app/ server package is never pulled into the wheel.
|
|
39
|
+
py-modules = ["slice_cli"]
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""The ``slice`` CLI (phase 12): log in from the terminal, then point tools at slice.
|
|
2
|
+
|
|
3
|
+
Installed as a console entry point via pyproject.toml, so ``pip install -e .`` gives the
|
|
4
|
+
``slice`` command. Three subcommands:
|
|
5
|
+
|
|
6
|
+
- ``slice login``, runs the GitHub device flow against the gateway's ``/auth/device/*``
|
|
7
|
+
endpoints (the gateway holds the OAuth client id and talks to GitHub; the CLI never
|
|
8
|
+
does). It shows the user code and URL, opens the browser, polls on the interval, and on
|
|
9
|
+
success saves the slice key and JWT to ``~/.slice/config.json`` (chmod 600).
|
|
10
|
+
- ``slice init``, confirms the saved config, records the default base URL, and prints
|
|
11
|
+
the current account status (a ``/auth/me`` call with the saved key).
|
|
12
|
+
- ``slice use <tool>``, prints the env lines that point a tool at slice. For tools where
|
|
13
|
+
the caller controls headers (SDK, curl) that is the whole story; for ``claude-code`` it
|
|
14
|
+
prints the three variables (base URL, your Anthropic key in ANTHROPIC_API_KEY, your slice
|
|
15
|
+
key in ANTHROPIC_AUTH_TOKEN) that run it end to end through slice.
|
|
16
|
+
|
|
17
|
+
The CLI talks only to the gateway over HTTP; it holds no secrets of its own. The saved
|
|
18
|
+
slice key is a bearer credential, so the config file is written 0600.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import stat
|
|
26
|
+
import time
|
|
27
|
+
import webbrowser
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
import httpx
|
|
31
|
+
import typer
|
|
32
|
+
|
|
33
|
+
app = typer.Typer(add_completion=False, help="slice. Log in and point your tools at the gateway.")
|
|
34
|
+
|
|
35
|
+
CONFIG_DIR = Path.home() / ".slice"
|
|
36
|
+
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
37
|
+
DEFAULT_BASE_URL = "http://localhost:8080"
|
|
38
|
+
|
|
39
|
+
# How the device flow gives up if the user never authorizes: the gateway returns the
|
|
40
|
+
# expiry, but cap the CLI's own patience too so a wedged terminal doesn't spin forever.
|
|
41
|
+
POLL_CEILING_SECONDS = 900
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# --- config file ------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def base_url() -> str:
|
|
48
|
+
"""The gateway base URL: the saved one, else ``SLICE_BASE_URL``, else the local default."""
|
|
49
|
+
saved = load_config().get("base_url")
|
|
50
|
+
return (saved or os.getenv("SLICE_BASE_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_config() -> dict:
|
|
54
|
+
try:
|
|
55
|
+
return json.loads(CONFIG_PATH.read_text())
|
|
56
|
+
except (FileNotFoundError, ValueError):
|
|
57
|
+
return {}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def save_config(data: dict) -> None:
|
|
61
|
+
"""Write ``~/.slice/config.json`` as 0600 (it holds a bearer credential)."""
|
|
62
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
CONFIG_PATH.write_text(json.dumps(data, indent=2) + "\n")
|
|
64
|
+
os.chmod(CONFIG_PATH, stat.S_IRUSR | stat.S_IWUSR) # 0600
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# --- commands ---------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.command()
|
|
71
|
+
def login(
|
|
72
|
+
base: str = typer.Option(None, "--base-url", help="Gateway base URL (default: saved / SLICE_BASE_URL / localhost)."),
|
|
73
|
+
open_browser: bool = typer.Option(True, "--open/--no-open", help="Open the verification URL in a browser."),
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Log in to slice via GitHub (device flow) and save the slice key."""
|
|
76
|
+
target = (base or base_url()).rstrip("/")
|
|
77
|
+
existing = load_config()
|
|
78
|
+
|
|
79
|
+
with httpx.Client(timeout=30.0) as http:
|
|
80
|
+
try:
|
|
81
|
+
started = http.post(f"{target}/auth/device/start")
|
|
82
|
+
except httpx.HTTPError as exc:
|
|
83
|
+
_fail(f"Could not reach the gateway at {target}: {exc}")
|
|
84
|
+
if started.status_code != 200:
|
|
85
|
+
_fail(f"Login could not start: {_message(started)}")
|
|
86
|
+
data = started.json()
|
|
87
|
+
session_id = data["session_id"]
|
|
88
|
+
user_code = data["user_code"]
|
|
89
|
+
verification_uri = data["verification_uri"]
|
|
90
|
+
interval = int(data.get("interval") or 5)
|
|
91
|
+
|
|
92
|
+
typer.echo("")
|
|
93
|
+
typer.secho(" To finish signing in, open:", bold=True)
|
|
94
|
+
typer.secho(f" {verification_uri}", fg=typer.colors.CYAN)
|
|
95
|
+
typer.secho(" and enter this code:", bold=True)
|
|
96
|
+
typer.secho(f" {user_code}", fg=typer.colors.GREEN, bold=True)
|
|
97
|
+
typer.echo("")
|
|
98
|
+
if open_browser:
|
|
99
|
+
try:
|
|
100
|
+
webbrowser.open(verification_uri)
|
|
101
|
+
except Exception: # noqa: BLE001, a headless box just shows the URL above.
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
typer.echo("Waiting for you to authorize in the browser…")
|
|
105
|
+
deadline = time.monotonic() + POLL_CEILING_SECONDS
|
|
106
|
+
while time.monotonic() < deadline:
|
|
107
|
+
time.sleep(max(1, interval))
|
|
108
|
+
try:
|
|
109
|
+
polled = http.post(f"{target}/auth/device/poll", json={"session_id": session_id})
|
|
110
|
+
except httpx.HTTPError as exc:
|
|
111
|
+
_fail(f"Lost contact with the gateway: {exc}")
|
|
112
|
+
if polled.status_code != 200:
|
|
113
|
+
_fail(f"Login failed: {_message(polled)}")
|
|
114
|
+
body = polled.json()
|
|
115
|
+
status = body.get("status")
|
|
116
|
+
if status == "authorized":
|
|
117
|
+
_save_login(target, existing, body)
|
|
118
|
+
account = body.get("account") or {}
|
|
119
|
+
typer.secho(f"Logged in as {account.get('login')}", fg=typer.colors.GREEN, bold=True)
|
|
120
|
+
return
|
|
121
|
+
if status == "slow_down":
|
|
122
|
+
interval = int(body.get("interval") or interval + 5)
|
|
123
|
+
continue
|
|
124
|
+
if status == "pending":
|
|
125
|
+
interval = int(body.get("interval") or interval)
|
|
126
|
+
continue
|
|
127
|
+
if status == "expired":
|
|
128
|
+
_fail("The login request expired before you authorized it. Run `slice login` again.")
|
|
129
|
+
if status == "denied":
|
|
130
|
+
_fail("Login was denied on the GitHub page.")
|
|
131
|
+
_fail(f"Unexpected login status: {status!r}")
|
|
132
|
+
_fail("Timed out waiting for authorization. Run `slice login` again.")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _save_login(target: str, existing: dict, body: dict) -> None:
|
|
136
|
+
account = body.get("account") or {}
|
|
137
|
+
save_config(
|
|
138
|
+
{
|
|
139
|
+
**existing,
|
|
140
|
+
"base_url": target,
|
|
141
|
+
"slice_key": body.get("slice_key"),
|
|
142
|
+
"jwt": body.get("jwt"),
|
|
143
|
+
"login": account.get("login"),
|
|
144
|
+
"account_id": account.get("id"),
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@app.command()
|
|
150
|
+
def init(
|
|
151
|
+
base: str = typer.Option(None, "--base-url", help="Set the default gateway base URL."),
|
|
152
|
+
) -> None:
|
|
153
|
+
"""Confirm the saved login, set the default base URL, and show account status."""
|
|
154
|
+
config = load_config()
|
|
155
|
+
if base:
|
|
156
|
+
config["base_url"] = base.rstrip("/")
|
|
157
|
+
save_config(config)
|
|
158
|
+
target = (config.get("base_url") or base_url()).rstrip("/")
|
|
159
|
+
key = config.get("slice_key")
|
|
160
|
+
|
|
161
|
+
typer.echo(f"Gateway: {target}")
|
|
162
|
+
typer.echo(f"Config: {CONFIG_PATH}")
|
|
163
|
+
if not key:
|
|
164
|
+
typer.secho("Not logged in. Run `slice login` first.", fg=typer.colors.YELLOW)
|
|
165
|
+
raise typer.Exit(code=1)
|
|
166
|
+
|
|
167
|
+
with httpx.Client(timeout=15.0) as http:
|
|
168
|
+
try:
|
|
169
|
+
me = http.get(f"{target}/auth/me", headers={"Authorization": f"Bearer {key}"})
|
|
170
|
+
except httpx.HTTPError as exc:
|
|
171
|
+
_fail(f"Could not reach the gateway at {target}: {exc}")
|
|
172
|
+
if me.status_code != 200:
|
|
173
|
+
typer.secho(f"Saved key was rejected: {_message(me)}", fg=typer.colors.RED)
|
|
174
|
+
typer.echo("Run `slice login` to get a fresh key.")
|
|
175
|
+
raise typer.Exit(code=1)
|
|
176
|
+
account = me.json().get("account") or {}
|
|
177
|
+
typer.secho(f"Logged in as {account.get('login')} (account {account.get('id')}).", fg=typer.colors.GREEN)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@app.command()
|
|
181
|
+
def use(tool: str = typer.Argument(..., help="Which tool: anthropic | openai | curl | claude-code")) -> None:
|
|
182
|
+
"""Print the environment lines that point a tool at slice."""
|
|
183
|
+
config = load_config()
|
|
184
|
+
target = base_url()
|
|
185
|
+
key = config.get("slice_key")
|
|
186
|
+
if not key:
|
|
187
|
+
typer.secho("Not logged in. Run `slice login` first.", fg=typer.colors.YELLOW)
|
|
188
|
+
raise typer.Exit(code=1)
|
|
189
|
+
|
|
190
|
+
name = tool.strip().lower()
|
|
191
|
+
if name in {"anthropic", "sdk", "python"}:
|
|
192
|
+
typer.echo(f"export ANTHROPIC_BASE_URL={target}")
|
|
193
|
+
typer.echo(f"export SLICE_KEY={key}")
|
|
194
|
+
typer.echo("# Send the slice key as the Authorization header, your provider key as x-api-key:")
|
|
195
|
+
typer.echo('# Authorization: Bearer $SLICE_KEY')
|
|
196
|
+
typer.echo('# x-api-key: <your Anthropic key>')
|
|
197
|
+
elif name in {"openai", "codex"}:
|
|
198
|
+
typer.echo(f"export OPENAI_BASE_URL={target}/v1")
|
|
199
|
+
typer.echo(f"export SLICE_KEY={key}")
|
|
200
|
+
typer.echo("# Authorization: Bearer $SLICE_KEY, and your provider key in x-api-key.")
|
|
201
|
+
elif name in {"curl", "http"}:
|
|
202
|
+
typer.echo(f'curl {target}/v1/messages \\')
|
|
203
|
+
typer.echo(f' -H "Authorization: Bearer {key}" \\')
|
|
204
|
+
typer.echo(' -H "x-api-key: $ANTHROPIC_API_KEY" \\')
|
|
205
|
+
typer.echo(' -H "content-type: application/json" \\')
|
|
206
|
+
typer.echo(' -d \'{"model":"claude-sonnet-5","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}\'')
|
|
207
|
+
elif name in {"claude-code", "claudecode", "cc"}:
|
|
208
|
+
typer.echo(f"export ANTHROPIC_BASE_URL={target}")
|
|
209
|
+
typer.echo("export ANTHROPIC_API_KEY=sk-ant-api... # your own Anthropic key")
|
|
210
|
+
typer.echo(f"export ANTHROPIC_AUTH_TOKEN={key} # your slice key, from slice login")
|
|
211
|
+
typer.echo("# ANTHROPIC_AUTH_TOKEN goes out as Authorization: Bearer, where slice reads its key.")
|
|
212
|
+
typer.echo("# ANTHROPIC_API_KEY stays your own Anthropic key in x-api-key; slice forwards it.")
|
|
213
|
+
typer.echo("# Claude Code notes that env auth takes precedence over your claude.ai login while")
|
|
214
|
+
typer.echo("# these are set, which is expected; unset the three variables to go back to normal.")
|
|
215
|
+
else:
|
|
216
|
+
_fail(f"Unknown tool {tool!r}. Try: anthropic | openai | curl | claude-code")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# --- helpers ----------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _message(response: httpx.Response) -> str:
|
|
223
|
+
try:
|
|
224
|
+
body = response.json()
|
|
225
|
+
except ValueError:
|
|
226
|
+
return f"HTTP {response.status_code}"
|
|
227
|
+
if isinstance(body, dict) and isinstance(body.get("error"), dict):
|
|
228
|
+
return body["error"].get("message") or f"HTTP {response.status_code}"
|
|
229
|
+
return f"HTTP {response.status_code}"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _fail(message: str) -> None:
|
|
233
|
+
typer.secho(message, fg=typer.colors.RED, err=True)
|
|
234
|
+
raise typer.Exit(code=1)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def main() -> None:
|
|
238
|
+
app()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
if __name__ == "__main__":
|
|
242
|
+
main()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slice-gateway
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line client for slice, the self-hosted LLM gateway.
|
|
5
|
+
Author: Jishnu Janardanan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://sliceapp.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/jjk30/slice
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Utilities
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: typer
|
|
17
|
+
Requires-Dist: httpx
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# slice-gateway
|
|
21
|
+
|
|
22
|
+
Command-line client for slice, a self-hosted LLM gateway that routes, caches, and caps your AI spend.
|
|
23
|
+
|
|
24
|
+
This package installs the `slice` command. It logs you in through GitHub, then prints the environment lines that point your existing tools at a slice gateway. It has no server dependencies. It needs only typer and httpx, plus the standard library.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
1. Install the CLI:
|
|
29
|
+
|
|
30
|
+
pip install slice-gateway
|
|
31
|
+
|
|
32
|
+
2. Log in through GitHub:
|
|
33
|
+
|
|
34
|
+
slice login
|
|
35
|
+
|
|
36
|
+
3. Point a tool at slice. For Claude Code, set three variables:
|
|
37
|
+
|
|
38
|
+
export ANTHROPIC_BASE_URL=https://api.sliceapp.dev
|
|
39
|
+
export ANTHROPIC_API_KEY=sk-ant-api... # your own Anthropic key
|
|
40
|
+
export ANTHROPIC_AUTH_TOKEN=slk_live_... # your slice key, from slice login
|
|
41
|
+
|
|
42
|
+
`ANTHROPIC_AUTH_TOKEN` goes out as `Authorization: Bearer`, which is where slice reads its key. `ANTHROPIC_API_KEY` stays your own Anthropic key in `x-api-key`, and slice forwards it upstream.
|
|
43
|
+
|
|
44
|
+
Run `slice use anthropic`, `slice use openai`, `slice use curl`, or `slice use claude-code` to print the exact lines for your tool.
|
|
45
|
+
|
|
46
|
+
## The gateway
|
|
47
|
+
|
|
48
|
+
This is only the client. The slice gateway itself, the server that does the routing, caching, budgets, and dashboards, lives on GitHub at https://github.com/jjk30/slice.
|
|
49
|
+
|
|
50
|
+
Homepage: https://sliceapp.dev
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
PYPI.md
|
|
4
|
+
pyproject.toml
|
|
5
|
+
slice_cli.py
|
|
6
|
+
slice_gateway.egg-info/PKG-INFO
|
|
7
|
+
slice_gateway.egg-info/SOURCES.txt
|
|
8
|
+
slice_gateway.egg-info/dependency_links.txt
|
|
9
|
+
slice_gateway.egg-info/entry_points.txt
|
|
10
|
+
slice_gateway.egg-info/requires.txt
|
|
11
|
+
slice_gateway.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
slice_cli
|