webbee 0.1.0__tar.gz → 0.1.2__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.
- webbee-0.1.2/CHANGELOG.md +39 -0
- {webbee-0.1.0 → webbee-0.1.2}/PKG-INFO +9 -5
- {webbee-0.1.0 → webbee-0.1.2}/README.md +7 -3
- {webbee-0.1.0 → webbee-0.1.2}/pyproject.toml +2 -2
- webbee-0.1.2/src/webbee/__init__.py +1 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/cli.py +1 -1
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/render.py +15 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/repl.py +12 -1
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_render.py +9 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_repl.py +17 -1
- webbee-0.1.0/CHANGELOG.md +0 -18
- webbee-0.1.0/src/webbee/__init__.py +0 -1
- {webbee-0.1.0 → webbee-0.1.2}/.github/workflows/publish.yml +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/.gitignore +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/LICENSE +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/install.sh +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/account.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/banner_art.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/commands.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/config.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/events.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/session.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/tools.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/tui.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/src/webbee/update.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/__init__.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_account.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_cli.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_commands.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_config.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_events.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_packaging.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_session.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_tools.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_tui.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_update.py +0 -0
- {webbee-0.1.0 → webbee-0.1.2}/tests/test_version.py +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
- **Device-code login (RFC 8628).** `/login` and `webbee login` now use the
|
|
6
|
+
device-authorization flow (via `imperal-mcp` 0.5.0): the terminal shows a
|
|
7
|
+
short code + `https://panel.imperal.io/device`; you approve in any browser
|
|
8
|
+
(even a phone), and the terminal polls until it's signed in. Works
|
|
9
|
+
identically on a local machine, over SSH, in WSL, or headless — no
|
|
10
|
+
`127.0.0.1` callback that a remote browser can never reach. Replaces the
|
|
11
|
+
loopback browser-login and the 0.1.1 executor workaround.
|
|
12
|
+
- The dock renders the sign-in code + URL into the action feed (a bare print is
|
|
13
|
+
invisible in the full-screen UI).
|
|
14
|
+
- Fix `__version__` so it tracks `pyproject.toml` (was pinned at 0.1.0).
|
|
15
|
+
|
|
16
|
+
## 0.1.1
|
|
17
|
+
|
|
18
|
+
- Fix `/login` inside the REPL: it now runs the shared `imperal_mcp` auth flow
|
|
19
|
+
off the event loop, so the browser sign-in completes instead of failing with
|
|
20
|
+
"asyncio.run() cannot be called from a running event loop". (`webbee login`
|
|
21
|
+
from the shell already worked; this fixes the in-REPL command too — one auth
|
|
22
|
+
mechanism for every surface.)
|
|
23
|
+
|
|
24
|
+
## 0.1.0
|
|
25
|
+
|
|
26
|
+
First public release.
|
|
27
|
+
|
|
28
|
+
- `webbee` — a coding agent in your terminal: reads, writes, and runs code in
|
|
29
|
+
the current directory; the brain runs in the Imperal Cloud on ICNLI. No model
|
|
30
|
+
keys on the machine.
|
|
31
|
+
- Full-screen dock: a scrollable, colored output pane with the input box and
|
|
32
|
+
toolbar pinned at the bottom.
|
|
33
|
+
- Consent modes — **default** (asks before anything it can't undo), **plan**
|
|
34
|
+
(read-only), **autopilot** (acts without asking); cycle with **Shift + TAB**.
|
|
35
|
+
Spending money always needs a browser approval.
|
|
36
|
+
- Reaches your connected Imperal apps (mail, notes, tasks, …) alongside the
|
|
37
|
+
local code tools.
|
|
38
|
+
- Slash commands: `/login` `/logout` `/mode` `/cost` `/status` `/clear` `/exit`.
|
|
39
|
+
- Live token/cost meter (session total) and update check.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: webbee
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Webbee 🐝 — the Imperal Cloud coding agent in your terminal
|
|
5
5
|
Project-URL: Homepage, https://imperal.io
|
|
6
6
|
Project-URL: Documentation, https://docs.imperal.io
|
|
@@ -24,7 +24,7 @@ Classifier: Topic :: Utilities
|
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
25
|
Requires-Dist: httpx-sse>=0.4
|
|
26
26
|
Requires-Dist: httpx>=0.27
|
|
27
|
-
Requires-Dist: imperal-mcp>=0.
|
|
27
|
+
Requires-Dist: imperal-mcp>=0.5.0
|
|
28
28
|
Requires-Dist: imperal-sdk>=5.9.2
|
|
29
29
|
Requires-Dist: prompt-toolkit>=3
|
|
30
30
|
Requires-Dist: rich>=13.7
|
|
@@ -44,15 +44,19 @@ Webbee is the [Imperal Cloud](https://imperal.io) coding agent, in your terminal
|
|
|
44
44
|
## Install
|
|
45
45
|
|
|
46
46
|
```sh
|
|
47
|
-
pipx install webbee # or: uv tool install webbee
|
|
47
|
+
pipx install webbee # recommended — or: uv tool install webbee
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Plain `pip` works too, inside a virtualenv:
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
-
|
|
53
|
+
python3 -m venv .venv && . .venv/bin/activate && pip install webbee
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
> On Ubuntu/Debian a *global* `pip install` is blocked by the system (that's
|
|
57
|
+
> [PEP 668](https://peps.python.org/pep-0668/), not webbee) — use `pipx` or a
|
|
58
|
+
> venv. No Python on the box? `curl -LsSf https://webbee.imperal.io/install.sh | sh`.
|
|
59
|
+
|
|
56
60
|
## Use
|
|
57
61
|
|
|
58
62
|
```sh
|
|
@@ -12,15 +12,19 @@ Webbee is the [Imperal Cloud](https://imperal.io) coding agent, in your terminal
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
pipx install webbee # or: uv tool install webbee
|
|
15
|
+
pipx install webbee # recommended — or: uv tool install webbee
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Plain `pip` works too, inside a virtualenv:
|
|
19
19
|
|
|
20
20
|
```sh
|
|
21
|
-
|
|
21
|
+
python3 -m venv .venv && . .venv/bin/activate && pip install webbee
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
> On Ubuntu/Debian a *global* `pip install` is blocked by the system (that's
|
|
25
|
+
> [PEP 668](https://peps.python.org/pep-0668/), not webbee) — use `pipx` or a
|
|
26
|
+
> venv. No Python on the box? `curl -LsSf https://webbee.imperal.io/install.sh | sh`.
|
|
27
|
+
|
|
24
28
|
## Use
|
|
25
29
|
|
|
26
30
|
```sh
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "webbee"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.2"
|
|
4
4
|
description = "Webbee 🐝 — the Imperal Cloud coding agent in your terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
7
7
|
license-files = ["LICENSE"]
|
|
8
8
|
requires-python = ">=3.11"
|
|
9
9
|
authors = [{ name = "Imperal, Inc.", email = "hello@imperal.io" }]
|
|
10
|
-
dependencies = ["imperal-mcp>=0.
|
|
10
|
+
dependencies = ["imperal-mcp>=0.5.0", "imperal-sdk>=5.9.2", "httpx>=0.27", "httpx-sse>=0.4", "rich>=13.7", "prompt_toolkit>=3"]
|
|
11
11
|
keywords = ["webbee", "imperal", "icnli", "coding-agent", "ai-agent", "cli", "terminal", "llm"]
|
|
12
12
|
classifiers = [
|
|
13
13
|
"Development Status :: 4 - Beta",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.2"
|
|
@@ -22,7 +22,7 @@ def main(argv=None) -> None:
|
|
|
22
22
|
|
|
23
23
|
if args.cmd == "login":
|
|
24
24
|
from imperal_mcp import auth
|
|
25
|
-
print(f"Logged in as {auth.
|
|
25
|
+
print(f"Logged in as {asyncio.run(auth.login_device(cfg))}.")
|
|
26
26
|
return
|
|
27
27
|
if args.cmd == "logout":
|
|
28
28
|
from imperal_mcp import auth
|
|
@@ -249,6 +249,21 @@ class RichSink:
|
|
|
249
249
|
self.console.print(Panel(body, title="💳 This costs money", border_style="magenta"))
|
|
250
250
|
self._nudge()
|
|
251
251
|
|
|
252
|
+
def login_prompt(self, user_code: str, url: str) -> None:
|
|
253
|
+
"""Device-code sign-in: show the URL to open + the code to enter, as a
|
|
254
|
+
clear framed block. A bare print would be invisible in the full-screen
|
|
255
|
+
dock, so this renders into the feed; the terminal then polls silently
|
|
256
|
+
until you authorize in the browser."""
|
|
257
|
+
body = Text.assemble(
|
|
258
|
+
("Open this URL in any browser (a phone is fine):\n", "white"),
|
|
259
|
+
(f" {url}\n\n", f"bold {_ACCENT}"),
|
|
260
|
+
("and enter this code:\n", "white"),
|
|
261
|
+
(f" {user_code}\n\n", f"bold {_BEE}"),
|
|
262
|
+
("Waiting for you to authorize…", "dim"),
|
|
263
|
+
)
|
|
264
|
+
self.console.print(Panel(body, title="🐝 Connect this terminal", border_style=_BEE))
|
|
265
|
+
self._nudge()
|
|
266
|
+
|
|
252
267
|
def progress(self, text: str) -> None:
|
|
253
268
|
if text:
|
|
254
269
|
self.console.print(Text(" " + text, style="dim italic"))
|
|
@@ -65,7 +65,18 @@ async def run_repl(cfg, mode: str = "default", *, sink=None, read_line=input,
|
|
|
65
65
|
if res.exit:
|
|
66
66
|
return "exit"
|
|
67
67
|
if res.action == "login":
|
|
68
|
-
|
|
68
|
+
# ONE shared imperal_mcp mechanism: device-code flow (RFC 8628),
|
|
69
|
+
# async, so we await it directly on the dock's event loop (the
|
|
70
|
+
# /login turn runs as a background task, so the dock stays
|
|
71
|
+
# responsive while it polls). on_prompt renders the code + URL
|
|
72
|
+
# into the feed — a bare print would be invisible in the dock.
|
|
73
|
+
def _login_prompt(user_code, uri, uri_complete):
|
|
74
|
+
show = getattr(_sink, "login_prompt", None)
|
|
75
|
+
if show:
|
|
76
|
+
show(user_code, uri)
|
|
77
|
+
else:
|
|
78
|
+
_sink.note(f"Open {uri} and enter code: {user_code}")
|
|
79
|
+
email = await auth.login_device(cfg, on_prompt=_login_prompt)
|
|
69
80
|
state["logged_in"] = True
|
|
70
81
|
_sink.note(f"Signed in as {email}.")
|
|
71
82
|
return "continue"
|
|
@@ -27,6 +27,15 @@ def test_tool_lines_render():
|
|
|
27
27
|
assert "read_file" in out and "auth.py" in out
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
def test_login_prompt_shows_code_and_url():
|
|
31
|
+
s = _sink()
|
|
32
|
+
s.login_prompt("WDBK-7Q3M", "https://panel.imperal.io/device")
|
|
33
|
+
out = s.console.export_text()
|
|
34
|
+
assert "WDBK-7Q3M" in out
|
|
35
|
+
assert "panel.imperal.io/device" in out
|
|
36
|
+
assert not NO_CYRILLIC.search(out) # English UI only
|
|
37
|
+
|
|
38
|
+
|
|
30
39
|
def test_ask_consent_relays_raw_input():
|
|
31
40
|
console = Console(record=True, width=80)
|
|
32
41
|
s = RichSink(console=console, live_enabled=False, input_fn=lambda p: " ага давай ", clock=lambda: 0.0)
|
|
@@ -41,7 +41,12 @@ class FakeAuth:
|
|
|
41
41
|
async def ensure_access_token(self, cfg):
|
|
42
42
|
if not self._in: raise self.NotLoggedInError("no creds")
|
|
43
43
|
return "tok"
|
|
44
|
-
def
|
|
44
|
+
async def login_device(self, cfg, *, on_prompt=None, open_browser=True):
|
|
45
|
+
if on_prompt:
|
|
46
|
+
on_prompt("WDBK-7Q3M", "https://panel.imperal.io/device",
|
|
47
|
+
"https://panel.imperal.io/device?code=WDBK-7Q3M")
|
|
48
|
+
self._in = True
|
|
49
|
+
return "u@imperal.io"
|
|
45
50
|
async def logout(self, cfg): self._in = False; self.logged_out = True
|
|
46
51
|
|
|
47
52
|
|
|
@@ -119,6 +124,17 @@ def test_login_command_calls_auth_and_logs_in():
|
|
|
119
124
|
assert any(not NO_CYRILLIC.search(n) for n in sink.notes)
|
|
120
125
|
|
|
121
126
|
|
|
127
|
+
def test_login_uses_device_flow_and_renders_prompt():
|
|
128
|
+
# Device-code flow (RFC 8628): repl awaits auth.login_device directly (async,
|
|
129
|
+
# no executor) and on_prompt renders the code + URL into the feed before the
|
|
130
|
+
# poll completes. Confirms the prompt reaches the sink and login persists.
|
|
131
|
+
auth = FakeAuth(logged_in=False)
|
|
132
|
+
sink, agent = _run(read_line=_lines("/login", "/exit"), auth=auth)
|
|
133
|
+
assert auth._in is True
|
|
134
|
+
assert any("WDBK-7Q3M" in n and "panel.imperal.io/device" in n for n in sink.notes)
|
|
135
|
+
assert any("Signed in as u@imperal.io" in n for n in sink.notes)
|
|
136
|
+
|
|
137
|
+
|
|
122
138
|
def test_mode_command_switches_agent_mode():
|
|
123
139
|
sink, agent = _run(read_line=_lines("/mode autopilot", "/exit"))
|
|
124
140
|
assert agent.mode == "autopilot"
|
webbee-0.1.0/CHANGELOG.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.1.0
|
|
4
|
-
|
|
5
|
-
First public release.
|
|
6
|
-
|
|
7
|
-
- `webbee` — a coding agent in your terminal: reads, writes, and runs code in
|
|
8
|
-
the current directory; the brain runs in the Imperal Cloud on ICNLI. No model
|
|
9
|
-
keys on the machine.
|
|
10
|
-
- Full-screen dock: a scrollable, colored output pane with the input box and
|
|
11
|
-
toolbar pinned at the bottom.
|
|
12
|
-
- Consent modes — **default** (asks before anything it can't undo), **plan**
|
|
13
|
-
(read-only), **autopilot** (acts without asking); cycle with **Shift + TAB**.
|
|
14
|
-
Spending money always needs a browser approval.
|
|
15
|
-
- Reaches your connected Imperal apps (mail, notes, tasks, …) alongside the
|
|
16
|
-
local code tools.
|
|
17
|
-
- Slash commands: `/login` `/logout` `/mode` `/cost` `/status` `/clear` `/exit`.
|
|
18
|
-
- Live token/cost meter (session total) and update check.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|