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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: weio-cli
3
- Version: 0.3.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
- Generate an API key in **Settings → API & CLI** on weio.ai, then:
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 # paste your weio_sk_… key (saved to ~/.weio/config.json)
42
- # or, per-session:
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
- Generate an API key in **Settings → API & CLI** on weio.ai, then:
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 # paste your weio_sk_… key (saved to ~/.weio/config.json)
20
- # or, per-session:
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.3.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"
@@ -1,3 +1,3 @@
1
1
  """Weio CLI — an agentic coding assistant that routes inference through your Weio account."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.4.0"
@@ -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'>&#10003; 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
- key = cli_key or input("Weio API key (weio_sk_…): ").strip()
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, config.resolve_base(cli_base))
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://api.weio.ai/v1)")
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], help="Save your API key")
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://api.weio.ai/v1"
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.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
- Generate an API key in **Settings → API & CLI** on weio.ai, then:
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 # paste your weio_sk_… key (saved to ~/.weio/config.json)
42
- # or, per-session:
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)
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/weio_cli/__init__.py
5
5
  src/weio_cli/__main__.py
6
+ src/weio_cli/browser_login.py
6
7
  src/weio_cli/cli.py
7
8
  src/weio_cli/client.py
8
9
  src/weio_cli/coder.py
File without changes
File without changes
File without changes
File without changes
File without changes