weio-cli 0.3.0__tar.gz → 0.4.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.
- {weio_cli-0.3.0/src/weio_cli.egg-info → weio_cli-0.4.0}/PKG-INFO +19 -4
- {weio_cli-0.3.0 → weio_cli-0.4.0}/README.md +18 -3
- {weio_cli-0.3.0 → weio_cli-0.4.0}/pyproject.toml +1 -1
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/__init__.py +1 -1
- weio_cli-0.4.0/src/weio_cli/browser_login.py +88 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/cli.py +57 -6
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/client.py +15 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/config.py +1 -1
- {weio_cli-0.3.0 → weio_cli-0.4.0/src/weio_cli.egg-info}/PKG-INFO +19 -4
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/SOURCES.txt +1 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/LICENSE +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/setup.cfg +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/__main__.py +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/coder.py +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/tui.py +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli/updater.py +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/dependency_links.txt +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/entry_points.txt +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/requires.txt +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/top_level.txt +0 -0
- {weio_cli-0.3.0 → weio_cli-0.4.0}/tests/test_coder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weio-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Weio — an agentic coding assistant that routes inference through your Weio account.
|
|
5
5
|
Author: We I/O Labs
|
|
6
6
|
License: MIT
|
|
@@ -35,14 +35,29 @@ Requires Python 3.9+.
|
|
|
35
35
|
|
|
36
36
|
## Authenticate
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
```bash
|
|
39
|
+
weio login # opens your browser, sign in (incl. Google), key is created automatically
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`weio login` starts a one-time local handshake, opens weio.ai in your browser to
|
|
43
|
+
sign in, mints an API key for this device, and saves it to `~/.weio/config.json`.
|
|
44
|
+
No copy-paste.
|
|
45
|
+
|
|
46
|
+
Prefer to paste a key yourself? Generate one in **Settings → API & CLI** on
|
|
47
|
+
weio.ai and:
|
|
39
48
|
|
|
40
49
|
```bash
|
|
41
|
-
weio login
|
|
42
|
-
# or, per
|
|
50
|
+
weio login --no-browser # paste your weio_sk_… key
|
|
51
|
+
# or, per session:
|
|
43
52
|
export WEIO_API_KEY="weio_sk_…"
|
|
44
53
|
```
|
|
45
54
|
|
|
55
|
+
Check what you're using:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
weio usage # tier, tokens used today, remaining, reset time
|
|
59
|
+
```
|
|
60
|
+
|
|
46
61
|
## Use
|
|
47
62
|
|
|
48
63
|
### Interactive TUI (default)
|
|
@@ -13,14 +13,29 @@ Requires Python 3.9+.
|
|
|
13
13
|
|
|
14
14
|
## Authenticate
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
```bash
|
|
17
|
+
weio login # opens your browser, sign in (incl. Google), key is created automatically
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`weio login` starts a one-time local handshake, opens weio.ai in your browser to
|
|
21
|
+
sign in, mints an API key for this device, and saves it to `~/.weio/config.json`.
|
|
22
|
+
No copy-paste.
|
|
23
|
+
|
|
24
|
+
Prefer to paste a key yourself? Generate one in **Settings → API & CLI** on
|
|
25
|
+
weio.ai and:
|
|
17
26
|
|
|
18
27
|
```bash
|
|
19
|
-
weio login
|
|
20
|
-
# or, per
|
|
28
|
+
weio login --no-browser # paste your weio_sk_… key
|
|
29
|
+
# or, per session:
|
|
21
30
|
export WEIO_API_KEY="weio_sk_…"
|
|
22
31
|
```
|
|
23
32
|
|
|
33
|
+
Check what you're using:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
weio usage # tier, tokens used today, remaining, reset time
|
|
37
|
+
```
|
|
38
|
+
|
|
24
39
|
## Use
|
|
25
40
|
|
|
26
41
|
### Interactive TUI (default)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weio-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Weio — an agentic coding assistant that routes inference through your Weio account."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Browser-based login: open Weio in the browser, sign in (incl. Google), and
|
|
2
|
+
receive a freshly-minted API key on a local loopback server. No manual key paste.
|
|
3
|
+
|
|
4
|
+
Standard OAuth-CLI loopback pattern (like gcloud / gh): we start a localhost
|
|
5
|
+
server, open the browser to the Weio authorize page with our port + a random
|
|
6
|
+
state, the page mints a key and redirects back to the loopback with it.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import http.server
|
|
11
|
+
import secrets
|
|
12
|
+
import socketserver
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
import urllib.parse
|
|
16
|
+
import webbrowser
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _web_base_from_api(api_base: str) -> str:
|
|
21
|
+
"""https://weio.ai/v1 -> https://weio.ai"""
|
|
22
|
+
b = (api_base or "").rstrip("/")
|
|
23
|
+
if b.endswith("/v1"):
|
|
24
|
+
b = b[:-3]
|
|
25
|
+
return b.rstrip("/")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_DONE_HTML = (
|
|
29
|
+
b"<!doctype html><html><head><meta charset='utf-8'><title>Weio CLI</title></head>"
|
|
30
|
+
b"<body style='font-family:-apple-system,Segoe UI,sans-serif;background:#0d1117;"
|
|
31
|
+
b"color:#e6edf3;display:flex;align-items:center;justify-content:center;height:100vh;margin:0'>"
|
|
32
|
+
b"<div style='text-align:center'><h2 style='color:#3fb950'>✓ Weio CLI authorized</h2>"
|
|
33
|
+
b"<p>You can close this tab and return to your terminal.</p></div></body></html>"
|
|
34
|
+
)
|
|
35
|
+
_FAIL_HTML = (
|
|
36
|
+
b"<!doctype html><html><body style='font-family:sans-serif'>"
|
|
37
|
+
b"<h2>Authorization failed.</h2><p>Please run <code>weio login</code> again.</p></body></html>"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def browser_login(api_base: str, timeout: float = 180.0) -> Optional[str]:
|
|
42
|
+
"""Open the browser and return the minted API key, or None on failure/timeout."""
|
|
43
|
+
state = secrets.token_urlsafe(16)
|
|
44
|
+
result: dict = {}
|
|
45
|
+
|
|
46
|
+
class Handler(http.server.BaseHTTPRequestHandler):
|
|
47
|
+
def do_GET(self): # noqa: N802
|
|
48
|
+
params = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
|
|
49
|
+
key = (params.get("key") or [None])[0]
|
|
50
|
+
st = (params.get("state") or [None])[0]
|
|
51
|
+
ok = bool(key) and st == state
|
|
52
|
+
self.send_response(200)
|
|
53
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
54
|
+
self.end_headers()
|
|
55
|
+
self.wfile.write(_DONE_HTML if ok else _FAIL_HTML)
|
|
56
|
+
if ok:
|
|
57
|
+
result["key"] = key
|
|
58
|
+
|
|
59
|
+
def log_message(self, *args): # silence
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
httpd = socketserver.TCPServer(("127.0.0.1", 0), Handler)
|
|
64
|
+
except OSError as e:
|
|
65
|
+
print(f"Could not start local auth server: {e}", file=sys.stderr)
|
|
66
|
+
return None
|
|
67
|
+
httpd.timeout = 1
|
|
68
|
+
port = httpd.server_address[1]
|
|
69
|
+
auth_url = (f"{_web_base_from_api(api_base)}/cli/auth"
|
|
70
|
+
f"?port={port}&state={urllib.parse.quote(state)}")
|
|
71
|
+
|
|
72
|
+
print("Opening your browser to sign in to Weio…")
|
|
73
|
+
print(f" {auth_url}")
|
|
74
|
+
print(" (If it doesn't open, copy that URL into your browser.)")
|
|
75
|
+
try:
|
|
76
|
+
webbrowser.open(auth_url)
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
deadline = time.time() + timeout
|
|
81
|
+
try:
|
|
82
|
+
while "key" not in result and time.time() < deadline:
|
|
83
|
+
httpd.handle_request()
|
|
84
|
+
except KeyboardInterrupt:
|
|
85
|
+
pass
|
|
86
|
+
finally:
|
|
87
|
+
httpd.server_close()
|
|
88
|
+
return result.get("key")
|
|
@@ -43,14 +43,33 @@ def _c(text: str, code: str) -> str:
|
|
|
43
43
|
def cmd_login(args) -> int:
|
|
44
44
|
cli_key = getattr(args, "key", None)
|
|
45
45
|
cli_base = getattr(args, "base", None)
|
|
46
|
-
|
|
46
|
+
base = config.resolve_base(cli_base)
|
|
47
|
+
no_browser = getattr(args, "no_browser", False)
|
|
48
|
+
|
|
49
|
+
key = cli_key
|
|
50
|
+
# Default: open the browser, sign in (incl. Google), auto-mint a key.
|
|
51
|
+
if not key and not no_browser:
|
|
52
|
+
try:
|
|
53
|
+
from .browser_login import browser_login
|
|
54
|
+
key = browser_login(base)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
print(_c(f"Browser sign-in unavailable ({e}).", "33"), file=sys.stderr)
|
|
57
|
+
if not key:
|
|
58
|
+
print(_c("Browser sign-in didn't complete. Falling back to manual key entry.", "33"))
|
|
59
|
+
# Fallback / explicit: paste a key from Settings → API & CLI.
|
|
60
|
+
if not key:
|
|
61
|
+
try:
|
|
62
|
+
key = input("Weio API key (weio_sk_…): ").strip()
|
|
63
|
+
except (EOFError, KeyboardInterrupt):
|
|
64
|
+
print()
|
|
65
|
+
return 2
|
|
47
66
|
if not key:
|
|
48
67
|
print("No key provided.", file=sys.stderr)
|
|
49
68
|
return 2
|
|
69
|
+
|
|
50
70
|
path = config.save(key=key, base=cli_base)
|
|
51
|
-
# Validate
|
|
52
71
|
try:
|
|
53
|
-
client = WeioClient(key,
|
|
72
|
+
client = WeioClient(key, base)
|
|
54
73
|
ok = client.ping()
|
|
55
74
|
except WeioError:
|
|
56
75
|
ok = False
|
|
@@ -128,6 +147,32 @@ def cmd_chat(args) -> int:
|
|
|
128
147
|
return 0
|
|
129
148
|
|
|
130
149
|
|
|
150
|
+
def cmd_usage(args) -> int:
|
|
151
|
+
try:
|
|
152
|
+
client = _make_client(args)
|
|
153
|
+
except WeioError as e:
|
|
154
|
+
print(_c(str(e), "31"), file=sys.stderr)
|
|
155
|
+
return 2
|
|
156
|
+
try:
|
|
157
|
+
u = client.usage()
|
|
158
|
+
except WeioError as e:
|
|
159
|
+
print(_c(str(e), "31"), file=sys.stderr)
|
|
160
|
+
return 1
|
|
161
|
+
used = u.get("daily_tokens_used", 0)
|
|
162
|
+
limit = u.get("daily_token_limit", 0)
|
|
163
|
+
remaining = u.get("daily_tokens_remaining", 0)
|
|
164
|
+
pct = (used / limit * 100) if limit else 0
|
|
165
|
+
bar_n = int(pct / 5)
|
|
166
|
+
bar = "█" * bar_n + "░" * (20 - bar_n)
|
|
167
|
+
print(_c(f" account ", "2") + f"{u.get('email','?')} ({u.get('tier','?')})")
|
|
168
|
+
print(_c(f" today ", "2") + f"{used:,} / {limit:,} tokens ({pct:.0f}%)")
|
|
169
|
+
print(_c(f" ", "2") + _c(bar, "32" if pct < 80 else "33"))
|
|
170
|
+
print(_c(f" remaining", "2") + f" {remaining:,} tokens")
|
|
171
|
+
if u.get("reset_at"):
|
|
172
|
+
print(_c(f" resets ", "2") + f"{u['reset_at']}")
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
|
|
131
176
|
def cmd_update(args) -> int:
|
|
132
177
|
from . import updater
|
|
133
178
|
latest = updater.check_for_update(force=True)
|
|
@@ -236,7 +281,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
236
281
|
common.add_argument("--key", "-k", default=argparse.SUPPRESS,
|
|
237
282
|
help="API key (or set WEIO_API_KEY / run `weio login`)")
|
|
238
283
|
common.add_argument("--base", default=argparse.SUPPRESS,
|
|
239
|
-
help="API base URL (default https://
|
|
284
|
+
help="API base URL (default https://weio.ai/v1)")
|
|
240
285
|
common.add_argument("--model", "-m", default=argparse.SUPPRESS, help="Model id (default: auto)")
|
|
241
286
|
common.add_argument("--max-tokens", type=int, default=argparse.SUPPRESS, help="Max output tokens")
|
|
242
287
|
|
|
@@ -249,7 +294,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
249
294
|
|
|
250
295
|
sub = p.add_subparsers(dest="command")
|
|
251
296
|
|
|
252
|
-
sp = sub.add_parser("login", parents=[common],
|
|
297
|
+
sp = sub.add_parser("login", parents=[common],
|
|
298
|
+
help="Sign in via browser (or --no-browser to paste a key)")
|
|
299
|
+
sp.add_argument("--no-browser", action="store_true",
|
|
300
|
+
help="Skip the browser flow and paste an API key manually")
|
|
253
301
|
sp.set_defaults(func=cmd_login)
|
|
254
302
|
|
|
255
303
|
sp = sub.add_parser("ping", parents=[common], help="Check connectivity and key")
|
|
@@ -267,6 +315,9 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
267
315
|
sp.add_argument("--dir", "-C", default=".", help="Project directory (default: cwd)")
|
|
268
316
|
sp.set_defaults(func=cmd_tui)
|
|
269
317
|
|
|
318
|
+
sp = sub.add_parser("usage", parents=[common], help="Show your token usage and limits")
|
|
319
|
+
sp.set_defaults(func=cmd_usage)
|
|
320
|
+
|
|
270
321
|
sp = sub.add_parser("update", parents=[common], help="Update weio-cli to the latest version")
|
|
271
322
|
sp.set_defaults(func=cmd_update)
|
|
272
323
|
|
|
@@ -284,7 +335,7 @@ def main(argv=None) -> int:
|
|
|
284
335
|
argv = list(sys.argv[1:] if argv is None else argv)
|
|
285
336
|
parser = build_parser()
|
|
286
337
|
|
|
287
|
-
known = {"login", "ping", "ask", "chat", "code", "tui", "update"}
|
|
338
|
+
known = {"login", "ping", "ask", "chat", "code", "tui", "update", "usage"}
|
|
288
339
|
# Bare `weio` (no args) → interactive coding TUI, like Claude Code.
|
|
289
340
|
if not argv:
|
|
290
341
|
argv = ["tui"]
|
|
@@ -108,6 +108,21 @@ class WeioClient:
|
|
|
108
108
|
except httpx.HTTPError as e:
|
|
109
109
|
raise WeioError(f"Network error talking to {self.base}: {e}") from e
|
|
110
110
|
|
|
111
|
+
def usage(self) -> dict:
|
|
112
|
+
"""Fetch account usage + limits."""
|
|
113
|
+
try:
|
|
114
|
+
r = httpx.get(f"{self.base}/usage", headers=self._headers(), timeout=15.0)
|
|
115
|
+
except httpx.HTTPError as e:
|
|
116
|
+
raise WeioError(f"Network error talking to {self.base}: {e}") from e
|
|
117
|
+
if r.status_code == 401:
|
|
118
|
+
raise WeioError("Authentication failed (401). Check your API key.")
|
|
119
|
+
if r.status_code >= 400:
|
|
120
|
+
raise WeioError(f"Gateway error {r.status_code}: {r.text[:200]}")
|
|
121
|
+
try:
|
|
122
|
+
return r.json()
|
|
123
|
+
except json.JSONDecodeError as e:
|
|
124
|
+
raise WeioError(f"Unexpected response: {r.text[:200]}") from e
|
|
125
|
+
|
|
111
126
|
def ping(self) -> bool:
|
|
112
127
|
"""Check the gateway is reachable and the key is valid (GET /models)."""
|
|
113
128
|
try:
|
|
@@ -13,7 +13,7 @@ import os
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from typing import Optional
|
|
15
15
|
|
|
16
|
-
DEFAULT_BASE = "https://
|
|
16
|
+
DEFAULT_BASE = "https://weio.ai/v1"
|
|
17
17
|
|
|
18
18
|
CONFIG_DIR = Path(os.environ.get("WEIO_CONFIG_DIR", str(Path.home() / ".weio")))
|
|
19
19
|
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weio-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Weio — an agentic coding assistant that routes inference through your Weio account.
|
|
5
5
|
Author: We I/O Labs
|
|
6
6
|
License: MIT
|
|
@@ -35,14 +35,29 @@ Requires Python 3.9+.
|
|
|
35
35
|
|
|
36
36
|
## Authenticate
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
```bash
|
|
39
|
+
weio login # opens your browser, sign in (incl. Google), key is created automatically
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`weio login` starts a one-time local handshake, opens weio.ai in your browser to
|
|
43
|
+
sign in, mints an API key for this device, and saves it to `~/.weio/config.json`.
|
|
44
|
+
No copy-paste.
|
|
45
|
+
|
|
46
|
+
Prefer to paste a key yourself? Generate one in **Settings → API & CLI** on
|
|
47
|
+
weio.ai and:
|
|
39
48
|
|
|
40
49
|
```bash
|
|
41
|
-
weio login
|
|
42
|
-
# or, per
|
|
50
|
+
weio login --no-browser # paste your weio_sk_… key
|
|
51
|
+
# or, per session:
|
|
43
52
|
export WEIO_API_KEY="weio_sk_…"
|
|
44
53
|
```
|
|
45
54
|
|
|
55
|
+
Check what you're using:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
weio usage # tier, tokens used today, remaining, reset time
|
|
59
|
+
```
|
|
60
|
+
|
|
46
61
|
## Use
|
|
47
62
|
|
|
48
63
|
### Interactive TUI (default)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|