weio-cli 0.2.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.2.0/src/weio_cli.egg-info → weio_cli-0.4.0}/PKG-INFO +32 -4
- {weio_cli-0.2.0 → weio_cli-0.4.0}/README.md +31 -3
- {weio_cli-0.2.0 → weio_cli-0.4.0}/pyproject.toml +1 -1
- {weio_cli-0.2.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.2.0 → weio_cli-0.4.0}/src/weio_cli/cli.py +82 -7
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli/client.py +15 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli/config.py +1 -1
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli/tui.py +7 -0
- weio_cli-0.4.0/src/weio_cli/updater.py +114 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0/src/weio_cli.egg-info}/PKG-INFO +32 -4
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/SOURCES.txt +2 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/LICENSE +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/setup.cfg +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli/__main__.py +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli/coder.py +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/dependency_links.txt +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/entry_points.txt +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/requires.txt +0 -0
- {weio_cli-0.2.0 → weio_cli-0.4.0}/src/weio_cli.egg-info/top_level.txt +0 -0
- {weio_cli-0.2.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)
|
|
@@ -93,6 +108,19 @@ weio ping
|
|
|
93
108
|
Edits are shown as a diff and require confirmation before anything is written
|
|
94
109
|
(use `-y`/`--yes` to apply automatically). New files are created as needed.
|
|
95
110
|
|
|
111
|
+
## Updating
|
|
112
|
+
|
|
113
|
+
weio-cli checks PyPI once a day (fail-silent) and prints a one-line notice when a
|
|
114
|
+
newer version is available. To upgrade:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
weio update # upgrades in place via pip
|
|
118
|
+
# or
|
|
119
|
+
pip install -U weio-cli
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Disable the check with `WEIO_NO_UPDATE_CHECK=1`.
|
|
123
|
+
|
|
96
124
|
## Configuration
|
|
97
125
|
|
|
98
126
|
| Setting | Flag | Env | Config file |
|
|
@@ -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)
|
|
@@ -71,6 +86,19 @@ weio ping
|
|
|
71
86
|
Edits are shown as a diff and require confirmation before anything is written
|
|
72
87
|
(use `-y`/`--yes` to apply automatically). New files are created as needed.
|
|
73
88
|
|
|
89
|
+
## Updating
|
|
90
|
+
|
|
91
|
+
weio-cli checks PyPI once a day (fail-silent) and prints a one-line notice when a
|
|
92
|
+
newer version is available. To upgrade:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
weio update # upgrades in place via pip
|
|
96
|
+
# or
|
|
97
|
+
pip install -U weio-cli
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Disable the check with `WEIO_NO_UPDATE_CHECK=1`.
|
|
101
|
+
|
|
74
102
|
## Configuration
|
|
75
103
|
|
|
76
104
|
| Setting | Flag | Env | Config file |
|
|
@@ -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,42 @@ 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
|
+
|
|
176
|
+
def cmd_update(args) -> int:
|
|
177
|
+
from . import updater
|
|
178
|
+
latest = updater.check_for_update(force=True)
|
|
179
|
+
if not latest:
|
|
180
|
+
print(_c(f"weio-cli {__version__} is up to date.", "32"))
|
|
181
|
+
return 0
|
|
182
|
+
print(_c(f"Updating weio-cli {__version__} → {latest}…", "36"))
|
|
183
|
+
return updater.self_update()
|
|
184
|
+
|
|
185
|
+
|
|
131
186
|
def cmd_tui(args) -> int:
|
|
132
187
|
try:
|
|
133
188
|
client = _make_client(args)
|
|
@@ -226,7 +281,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
226
281
|
common.add_argument("--key", "-k", default=argparse.SUPPRESS,
|
|
227
282
|
help="API key (or set WEIO_API_KEY / run `weio login`)")
|
|
228
283
|
common.add_argument("--base", default=argparse.SUPPRESS,
|
|
229
|
-
help="API base URL (default https://
|
|
284
|
+
help="API base URL (default https://weio.ai/v1)")
|
|
230
285
|
common.add_argument("--model", "-m", default=argparse.SUPPRESS, help="Model id (default: auto)")
|
|
231
286
|
common.add_argument("--max-tokens", type=int, default=argparse.SUPPRESS, help="Max output tokens")
|
|
232
287
|
|
|
@@ -239,7 +294,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
239
294
|
|
|
240
295
|
sub = p.add_subparsers(dest="command")
|
|
241
296
|
|
|
242
|
-
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")
|
|
243
301
|
sp.set_defaults(func=cmd_login)
|
|
244
302
|
|
|
245
303
|
sp = sub.add_parser("ping", parents=[common], help="Check connectivity and key")
|
|
@@ -257,6 +315,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
257
315
|
sp.add_argument("--dir", "-C", default=".", help="Project directory (default: cwd)")
|
|
258
316
|
sp.set_defaults(func=cmd_tui)
|
|
259
317
|
|
|
318
|
+
sp = sub.add_parser("usage", parents=[common], help="Show your token usage and limits")
|
|
319
|
+
sp.set_defaults(func=cmd_usage)
|
|
320
|
+
|
|
321
|
+
sp = sub.add_parser("update", parents=[common], help="Update weio-cli to the latest version")
|
|
322
|
+
sp.set_defaults(func=cmd_update)
|
|
323
|
+
|
|
260
324
|
sp = sub.add_parser("code", parents=[common], help="Run a coding task in a directory")
|
|
261
325
|
sp.add_argument("message")
|
|
262
326
|
sp.add_argument("--file", "-f", action="append", help="Add a file to the context (repeatable)")
|
|
@@ -271,7 +335,7 @@ def main(argv=None) -> int:
|
|
|
271
335
|
argv = list(sys.argv[1:] if argv is None else argv)
|
|
272
336
|
parser = build_parser()
|
|
273
337
|
|
|
274
|
-
known = {"login", "ping", "ask", "chat", "code", "tui"}
|
|
338
|
+
known = {"login", "ping", "ask", "chat", "code", "tui", "update", "usage"}
|
|
275
339
|
# Bare `weio` (no args) → interactive coding TUI, like Claude Code.
|
|
276
340
|
if not argv:
|
|
277
341
|
argv = ["tui"]
|
|
@@ -284,10 +348,21 @@ def main(argv=None) -> int:
|
|
|
284
348
|
parser.print_help()
|
|
285
349
|
return 1
|
|
286
350
|
try:
|
|
287
|
-
|
|
351
|
+
rc = args.func(args)
|
|
288
352
|
except KeyboardInterrupt:
|
|
289
353
|
print()
|
|
290
354
|
return 130
|
|
355
|
+
# Throttled, fail-silent update notice (skipped for tui — it shows its own
|
|
356
|
+
# in the header — and for the update command itself).
|
|
357
|
+
if args.command not in ("update", "tui"):
|
|
358
|
+
try:
|
|
359
|
+
from . import updater
|
|
360
|
+
notice = updater.update_notice()
|
|
361
|
+
if notice:
|
|
362
|
+
print(_c("\n" + notice, "33"), file=sys.stderr)
|
|
363
|
+
except Exception:
|
|
364
|
+
pass
|
|
365
|
+
return rc
|
|
291
366
|
|
|
292
367
|
|
|
293
368
|
if __name__ == "__main__":
|
|
@@ -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"
|
|
@@ -283,6 +283,13 @@ class Repl:
|
|
|
283
283
|
print(_c(f" {self.client.base} · " +
|
|
284
284
|
("connected" if ok else "⚠ cannot reach gateway / bad key"),
|
|
285
285
|
GREEN if ok else RED))
|
|
286
|
+
try:
|
|
287
|
+
from .updater import update_notice
|
|
288
|
+
notice = update_notice()
|
|
289
|
+
if notice:
|
|
290
|
+
print(_c(" ⬆ " + notice, YELLOW))
|
|
291
|
+
except Exception:
|
|
292
|
+
pass
|
|
286
293
|
print(_c(" Type a request, or /help. Ctrl-D to exit.\n", DIM))
|
|
287
294
|
while True:
|
|
288
295
|
text = self._read()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Update checking + self-update.
|
|
2
|
+
|
|
3
|
+
A throttled, fail-silent check against PyPI (once per day) tells the user when a
|
|
4
|
+
newer weio-cli is published. `weio update` upgrades in place. Disable the check
|
|
5
|
+
with WEIO_NO_UPDATE_CHECK=1.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
from . import __version__
|
|
18
|
+
|
|
19
|
+
INDEX_JSON = os.environ.get("WEIO_UPDATE_INDEX", "https://pypi.org/pypi/weio-cli/json")
|
|
20
|
+
_CACHE_NAME = "update_check.json"
|
|
21
|
+
_TTL_HOURS = 24
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cache_dir() -> Path:
|
|
25
|
+
return Path(os.environ.get("WEIO_CONFIG_DIR", str(Path.home() / ".weio")))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _parse_version(v: str) -> tuple:
|
|
29
|
+
"""Lenient numeric version tuple, e.g. '0.10.2' -> (0, 10, 2)."""
|
|
30
|
+
out = []
|
|
31
|
+
for part in (v or "").split("."):
|
|
32
|
+
num = ""
|
|
33
|
+
for ch in part:
|
|
34
|
+
if ch.isdigit():
|
|
35
|
+
num += ch
|
|
36
|
+
else:
|
|
37
|
+
break
|
|
38
|
+
out.append(int(num) if num else 0)
|
|
39
|
+
return tuple(out) or (0,)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_newer(latest: str, current: str) -> bool:
|
|
43
|
+
return _parse_version(latest) > _parse_version(current)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def fetch_latest(timeout: float = 2.5) -> Optional[str]:
|
|
47
|
+
try:
|
|
48
|
+
import httpx
|
|
49
|
+
r = httpx.get(INDEX_JSON, timeout=timeout,
|
|
50
|
+
headers={"User-Agent": f"weio-cli/{__version__}"})
|
|
51
|
+
if r.status_code != 200:
|
|
52
|
+
return None
|
|
53
|
+
return r.json()["info"]["version"]
|
|
54
|
+
except Exception:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_for_update(current: str = __version__, *, ttl_hours: int = _TTL_HOURS,
|
|
59
|
+
force: bool = False) -> Optional[str]:
|
|
60
|
+
"""Return the latest version string if newer than `current`, else None.
|
|
61
|
+
|
|
62
|
+
Throttled to once per ttl_hours via a cache file; never raises.
|
|
63
|
+
"""
|
|
64
|
+
if os.environ.get("WEIO_NO_UPDATE_CHECK") == "1":
|
|
65
|
+
return None
|
|
66
|
+
try:
|
|
67
|
+
cache = _cache_dir() / _CACHE_NAME
|
|
68
|
+
data = {}
|
|
69
|
+
if cache.exists():
|
|
70
|
+
try:
|
|
71
|
+
data = json.loads(cache.read_text())
|
|
72
|
+
except Exception:
|
|
73
|
+
data = {}
|
|
74
|
+
now = time.time()
|
|
75
|
+
latest = data.get("latest")
|
|
76
|
+
last = float(data.get("last_check", 0) or 0)
|
|
77
|
+
|
|
78
|
+
if force or not latest or (now - last) > ttl_hours * 3600:
|
|
79
|
+
fetched = fetch_latest()
|
|
80
|
+
if fetched:
|
|
81
|
+
latest = fetched
|
|
82
|
+
try:
|
|
83
|
+
cache.parent.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
cache.write_text(json.dumps({"last_check": now, "latest": latest}))
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
if latest and is_newer(latest, current):
|
|
89
|
+
return latest
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def update_notice(current: str = __version__) -> Optional[str]:
|
|
96
|
+
"""One-line, color-free notice if an update is available (for printing)."""
|
|
97
|
+
latest = check_for_update(current)
|
|
98
|
+
if not latest:
|
|
99
|
+
return None
|
|
100
|
+
return (f"weio-cli {latest} is available (you have {current}). "
|
|
101
|
+
f"Update with: weio update")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def self_update(pre: bool = False) -> int:
|
|
105
|
+
"""Upgrade weio-cli in place using the running interpreter's pip."""
|
|
106
|
+
cmd = [sys.executable, "-m", "pip", "install", "--upgrade", "weio-cli"]
|
|
107
|
+
if pre:
|
|
108
|
+
cmd.append("--pre")
|
|
109
|
+
print(f"Running: {' '.join(cmd)}")
|
|
110
|
+
try:
|
|
111
|
+
return subprocess.call(cmd)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
print(f"Update failed: {e}", file=sys.stderr)
|
|
114
|
+
return 1
|
|
@@ -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)
|
|
@@ -93,6 +108,19 @@ weio ping
|
|
|
93
108
|
Edits are shown as a diff and require confirmation before anything is written
|
|
94
109
|
(use `-y`/`--yes` to apply automatically). New files are created as needed.
|
|
95
110
|
|
|
111
|
+
## Updating
|
|
112
|
+
|
|
113
|
+
weio-cli checks PyPI once a day (fail-silent) and prints a one-line notice when a
|
|
114
|
+
newer version is available. To upgrade:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
weio update # upgrades in place via pip
|
|
118
|
+
# or
|
|
119
|
+
pip install -U weio-cli
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Disable the check with `WEIO_NO_UPDATE_CHECK=1`.
|
|
123
|
+
|
|
96
124
|
## Configuration
|
|
97
125
|
|
|
98
126
|
| Setting | Flag | Env | Config file |
|
|
@@ -3,11 +3,13 @@ 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
|
|
9
10
|
src/weio_cli/config.py
|
|
10
11
|
src/weio_cli/tui.py
|
|
12
|
+
src/weio_cli/updater.py
|
|
11
13
|
src/weio_cli.egg-info/PKG-INFO
|
|
12
14
|
src/weio_cli.egg-info/SOURCES.txt
|
|
13
15
|
src/weio_cli.egg-info/dependency_links.txt
|
|
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
|