webbee 0.3.29__tar.gz → 0.3.31__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.
Files changed (120) hide show
  1. {webbee-0.3.29 → webbee-0.3.31}/CHANGELOG.md +12 -0
  2. {webbee-0.3.29 → webbee-0.3.31}/PKG-INFO +1 -1
  3. {webbee-0.3.29 → webbee-0.3.31}/pyproject.toml +1 -1
  4. webbee-0.3.31/src/webbee/__init__.py +1 -0
  5. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/home_view.py +3 -4
  6. webbee-0.3.31/src/webbee/urlopen.py +37 -0
  7. {webbee-0.3.29 → webbee-0.3.31}/tests/test_home_view.py +6 -5
  8. webbee-0.3.31/tests/test_urlopen.py +50 -0
  9. webbee-0.3.29/src/webbee/__init__.py +0 -1
  10. webbee-0.3.29/src/webbee/urlopen.py +0 -16
  11. webbee-0.3.29/tests/test_urlopen.py +0 -18
  12. {webbee-0.3.29 → webbee-0.3.31}/.github/workflows/publish.yml +0 -0
  13. {webbee-0.3.29 → webbee-0.3.31}/.gitignore +0 -0
  14. {webbee-0.3.29 → webbee-0.3.31}/LICENSE +0 -0
  15. {webbee-0.3.29 → webbee-0.3.31}/README.md +0 -0
  16. {webbee-0.3.29 → webbee-0.3.31}/docs/superpowers/plans/2026-07-21-interactive-home-dashboard.md +0 -0
  17. {webbee-0.3.29 → webbee-0.3.31}/install.sh +0 -0
  18. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/account.py +0 -0
  19. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/active_sessions.py +0 -0
  20. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/banner_art.py +0 -0
  21. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/boot.py +0 -0
  22. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/checkpoints.py +0 -0
  23. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/cli.py +0 -0
  24. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/clipboard.py +0 -0
  25. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/coding_context.py +0 -0
  26. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/commands.py +0 -0
  27. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/config.py +0 -0
  28. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/consent.py +0 -0
  29. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/details.py +0 -0
  30. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/events.py +0 -0
  31. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/frames.py +0 -0
  32. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/home.py +0 -0
  33. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/http.py +0 -0
  34. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/instance_lock.py +0 -0
  35. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/__init__.py +0 -0
  36. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/chunker.py +0 -0
  37. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/embed.py +0 -0
  38. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/graph.py +0 -0
  39. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/indexer.py +0 -0
  40. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/models.py +0 -0
  41. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/query.py +0 -0
  42. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/service.py +0 -0
  43. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/store.py +0 -0
  44. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/vectors.py +0 -0
  45. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/intel/watch.py +0 -0
  46. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/mode_store.py +0 -0
  47. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/newtab_mode.py +0 -0
  48. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/output_pane.py +0 -0
  49. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/queue_panel.py +0 -0
  50. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/reflow.py +0 -0
  51. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/remote.py +0 -0
  52. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/render.py +0 -0
  53. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/repl.py +0 -0
  54. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/repo.py +0 -0
  55. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/selection.py +0 -0
  56. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/session.py +0 -0
  57. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/sessions.py +0 -0
  58. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/sizing.py +0 -0
  59. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/slots.py +0 -0
  60. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/steer.py +0 -0
  61. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/stream.py +0 -0
  62. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/tabs.py +0 -0
  63. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/thread.py +0 -0
  64. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/todo_panel.py +0 -0
  65. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/tokens.py +0 -0
  66. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/tools.py +0 -0
  67. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/tui.py +0 -0
  68. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/update.py +0 -0
  69. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/wallet.py +0 -0
  70. {webbee-0.3.29 → webbee-0.3.31}/src/webbee/worktrees.py +0 -0
  71. {webbee-0.3.29 → webbee-0.3.31}/tests/__init__.py +0 -0
  72. {webbee-0.3.29 → webbee-0.3.31}/tests/conftest.py +0 -0
  73. {webbee-0.3.29 → webbee-0.3.31}/tests/test_account.py +0 -0
  74. {webbee-0.3.29 → webbee-0.3.31}/tests/test_active_sessions.py +0 -0
  75. {webbee-0.3.29 → webbee-0.3.31}/tests/test_checkpoints.py +0 -0
  76. {webbee-0.3.29 → webbee-0.3.31}/tests/test_cli.py +0 -0
  77. {webbee-0.3.29 → webbee-0.3.31}/tests/test_clipboard.py +0 -0
  78. {webbee-0.3.29 → webbee-0.3.31}/tests/test_commands.py +0 -0
  79. {webbee-0.3.29 → webbee-0.3.31}/tests/test_config.py +0 -0
  80. {webbee-0.3.29 → webbee-0.3.31}/tests/test_cpc_contract_stable.py +0 -0
  81. {webbee-0.3.29 → webbee-0.3.31}/tests/test_details.py +0 -0
  82. {webbee-0.3.29 → webbee-0.3.31}/tests/test_events.py +0 -0
  83. {webbee-0.3.29 → webbee-0.3.31}/tests/test_freeze_fix.py +0 -0
  84. {webbee-0.3.29 → webbee-0.3.31}/tests/test_home.py +0 -0
  85. {webbee-0.3.29 → webbee-0.3.31}/tests/test_instance_lock.py +0 -0
  86. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_chunker.py +0 -0
  87. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_embed.py +0 -0
  88. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_graph.py +0 -0
  89. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_indexer.py +0 -0
  90. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_no_numpy.py +0 -0
  91. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_query.py +0 -0
  92. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_service.py +0 -0
  93. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_store.py +0 -0
  94. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_vectors.py +0 -0
  95. {webbee-0.3.29 → webbee-0.3.31}/tests/test_intel_watch.py +0 -0
  96. {webbee-0.3.29 → webbee-0.3.31}/tests/test_marathon.py +0 -0
  97. {webbee-0.3.29 → webbee-0.3.31}/tests/test_mode_store.py +0 -0
  98. {webbee-0.3.29 → webbee-0.3.31}/tests/test_newtab_mode.py +0 -0
  99. {webbee-0.3.29 → webbee-0.3.31}/tests/test_packaging.py +0 -0
  100. {webbee-0.3.29 → webbee-0.3.31}/tests/test_reflow.py +0 -0
  101. {webbee-0.3.29 → webbee-0.3.31}/tests/test_render.py +0 -0
  102. {webbee-0.3.29 → webbee-0.3.31}/tests/test_repl.py +0 -0
  103. {webbee-0.3.29 → webbee-0.3.31}/tests/test_repo.py +0 -0
  104. {webbee-0.3.29 → webbee-0.3.31}/tests/test_session.py +0 -0
  105. {webbee-0.3.29 → webbee-0.3.31}/tests/test_sessions.py +0 -0
  106. {webbee-0.3.29 → webbee-0.3.31}/tests/test_sizing.py +0 -0
  107. {webbee-0.3.29 → webbee-0.3.31}/tests/test_slots.py +0 -0
  108. {webbee-0.3.29 → webbee-0.3.31}/tests/test_steer.py +0 -0
  109. {webbee-0.3.29 → webbee-0.3.31}/tests/test_stream.py +0 -0
  110. {webbee-0.3.29 → webbee-0.3.31}/tests/test_tabs.py +0 -0
  111. {webbee-0.3.29 → webbee-0.3.31}/tests/test_thread.py +0 -0
  112. {webbee-0.3.29 → webbee-0.3.31}/tests/test_todo_panel.py +0 -0
  113. {webbee-0.3.29 → webbee-0.3.31}/tests/test_tokens.py +0 -0
  114. {webbee-0.3.29 → webbee-0.3.31}/tests/test_tools.py +0 -0
  115. {webbee-0.3.29 → webbee-0.3.31}/tests/test_tui.py +0 -0
  116. {webbee-0.3.29 → webbee-0.3.31}/tests/test_tui_hardening.py +0 -0
  117. {webbee-0.3.29 → webbee-0.3.31}/tests/test_update.py +0 -0
  118. {webbee-0.3.29 → webbee-0.3.31}/tests/test_version.py +0 -0
  119. {webbee-0.3.29 → webbee-0.3.31}/tests/test_wallet.py +0 -0
  120. {webbee-0.3.29 → webbee-0.3.31}/tests/test_worktrees.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.31
4
+
5
+ - Fixed a Linux bug where "Top up credits" / "Read our security" could dump a
6
+ browser's startup logs into the terminal and scramble the screen. The
7
+ browser now launches fully detached and silent; on a headless/SSH session
8
+ we simply show the link to copy.
9
+
10
+ ## 0.3.30
11
+
12
+ - Removed the stray "◆ Home" title from the top of the Home screen — Home now
13
+ opens straight into your tiles.
14
+
3
15
  ## 0.3.29
4
16
 
5
17
  - The Home screen scrolls for real now — mouse wheel and PageUp/PageDown move
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webbee
3
- Version: 0.3.29
3
+ Version: 0.3.31
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "webbee"
3
- version = "0.3.29"
3
+ version = "0.3.31"
4
4
  description = "Webbee 🐝 — the Imperal Cloud coding agent in your terminal"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -0,0 +1 @@
1
+ __version__ = "0.3.31"
@@ -596,10 +596,9 @@ class HomeView:
596
596
  "· Ctrl+T new tab · Alt+N switch")])
597
597
  return L
598
598
 
599
- # A small centered "◆ Home" title. The Webbee Code logo now opens each
600
- # NEW session tab instead (RichSink.banner), so Home itself stays clean.
601
- title_pad = max(0, (width - len("◆ Home")) // 2)
602
- lines = [[("", "")], [("", " " * title_pad), ("class:home.header", "◆ Home")], [("", "")]]
599
+ # No center title the tab bar's Home chip already names the tab,
600
+ # and the tiles below are Home's content. Just a leading blank line.
601
+ lines = [[("", "")]]
603
602
  if two_column(width):
604
603
  colw = max(20, (width // 2) - 3)
605
604
  lines += _side_by_side(you_lines(), wallet_lines(), colw)
@@ -0,0 +1,37 @@
1
+ """Best-effort URL opener (W5 Home: Top-up credits, Read security docs).
2
+
3
+ The browser is launched FULLY DETACHED and SILENCED — its own session, with
4
+ stdin/stdout/stderr all routed to /dev/null — so a chatty browser can NEVER
5
+ spew into and corrupt the full-screen terminal. (A real Ubuntu user clicked
6
+ "Top up credits" and Chrome's `MESA-LOADER` / GCM / lens-overlay JS logs
7
+ poured straight into the TUI, scrambling it — that inherited-fd path is what
8
+ this guards against.) On a pure SSH / headless Linux session there is no
9
+ browser to open, so we don't even try — the caller ALWAYS shows the URL as
10
+ copyable text, which is the reliable path there. Returns the URL unchanged so
11
+ the caller can display it; never raises, never blocks."""
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import subprocess
16
+ import sys
17
+
18
+
19
+ def open_url(url: str) -> str:
20
+ try:
21
+ if sys.platform == "darwin":
22
+ argv = ["open", url]
23
+ elif os.name == "nt":
24
+ argv = ["cmd", "/c", "start", "", url]
25
+ else:
26
+ # Linux / BSD: only attempt a launch when a graphical session is
27
+ # actually present. No DISPLAY/WAYLAND == SSH/headless == nothing
28
+ # to open (and nothing that could pollute the terminal).
29
+ if not (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY")):
30
+ return url
31
+ argv = ["xdg-open", url]
32
+ devnull = subprocess.DEVNULL
33
+ subprocess.Popen(argv, stdin=devnull, stdout=devnull, stderr=devnull,
34
+ start_new_session=True)
35
+ except Exception:
36
+ pass
37
+ return url
@@ -297,13 +297,14 @@ def test_you_tile_shows_expires_when_not_active():
297
297
  assert "expires Aug 3" in text
298
298
 
299
299
 
300
- def test_home_has_title_but_no_logo():
301
- # 0.3.29: the Webbee Code logo moved to each NEW session tab; Home keeps
302
- # only a small "◆ Home" title.
300
+ def test_home_has_no_logo_and_no_center_title():
301
+ # The Webbee Code logo moved to each NEW session tab; the out-of-place
302
+ # center "◆ Home" title is gone too. Home's content starts with the tiles.
303
303
  hv, _ = _view()
304
304
  text = "".join(f[1] for f in hv._fragments())
305
- assert "◆ Home" in text
306
- assert "_____" not in text # the ascii logo is no longer on Home
305
+ assert "_____" not in text # no ascii logo on Home
306
+ assert "◆ Home" not in text # no center title (the tab-bar chip still has it)
307
+ assert "You" in text # the tiles are the top of Home
307
308
 
308
309
 
309
310
  def test_home_virtualized_scroll_slices_and_clamps():
@@ -0,0 +1,50 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+ import webbee.urlopen as urlopen
6
+
7
+
8
+ def test_open_url_launches_detached_and_silenced(monkeypatch):
9
+ # Force the darwin path so a launch is always attempted regardless of the
10
+ # test host, then assert the child's std streams are ALL silenced (so a
11
+ # chatty browser can't corrupt the TUI) and it's detached.
12
+ monkeypatch.setattr(sys, "platform", "darwin")
13
+ calls = {}
14
+
15
+ def fake_popen(argv, **kw):
16
+ calls["argv"] = argv
17
+ calls["kw"] = kw
18
+ return object()
19
+
20
+ monkeypatch.setattr(subprocess, "Popen", fake_popen)
21
+ out = urlopen.open_url("https://panel.imperal.io/ext/billing")
22
+ assert out == "https://panel.imperal.io/ext/billing"
23
+ assert "https://panel.imperal.io/ext/billing" in calls["argv"]
24
+ assert calls["kw"]["stdin"] == subprocess.DEVNULL
25
+ assert calls["kw"]["stdout"] == subprocess.DEVNULL
26
+ assert calls["kw"]["stderr"] == subprocess.DEVNULL
27
+ assert calls["kw"].get("start_new_session") is True
28
+
29
+
30
+ def test_open_url_skips_launch_on_headless_linux(monkeypatch):
31
+ # No DISPLAY/WAYLAND on Linux == SSH/headless: don't spawn anything, just
32
+ # return the URL for the caller to show as copyable text.
33
+ monkeypatch.setattr(sys, "platform", "linux")
34
+ monkeypatch.setattr(os, "name", "posix")
35
+ monkeypatch.delenv("DISPLAY", raising=False)
36
+ monkeypatch.delenv("WAYLAND_DISPLAY", raising=False)
37
+ called = []
38
+ monkeypatch.setattr(subprocess, "Popen", lambda *a, **k: called.append(1))
39
+ assert urlopen.open_url("https://x") == "https://x"
40
+ assert called == []
41
+
42
+
43
+ def test_open_url_never_raises(monkeypatch):
44
+ monkeypatch.setattr(sys, "platform", "darwin")
45
+
46
+ def boom(*a, **k):
47
+ raise RuntimeError("no opener")
48
+
49
+ monkeypatch.setattr(subprocess, "Popen", boom)
50
+ assert urlopen.open_url("https://x") == "https://x"
@@ -1 +0,0 @@
1
- __version__ = "0.3.29"
@@ -1,16 +0,0 @@
1
- """Best-effort URL opener (W5 Home: Top-up credits, Read security docs).
2
- There is no existing opener in the client -- this is the minimal one. On a
3
- LOCAL machine `webbrowser.open` launches the default browser; over SSH (the
4
- common Webbee Code case) it no-ops or fails, so the caller ALSO surfaces the
5
- URL as copyable text. Returns the URL unchanged so the caller can show it;
6
- never raises."""
7
- from __future__ import annotations
8
-
9
-
10
- def open_url(url: str) -> str:
11
- try:
12
- import webbrowser
13
- webbrowser.open(url, new=2)
14
- except Exception:
15
- pass
16
- return url
@@ -1,18 +0,0 @@
1
- import webbee.urlopen as urlopen
2
-
3
-
4
- def test_open_url_returns_url_and_calls_webbrowser(monkeypatch):
5
- calls = []
6
- import webbrowser
7
- monkeypatch.setattr(webbrowser, "open", lambda u, new=2: calls.append(u))
8
- out = urlopen.open_url("https://panel.imperal.io/billing")
9
- assert out == "https://panel.imperal.io/billing"
10
- assert calls == ["https://panel.imperal.io/billing"]
11
-
12
-
13
- def test_open_url_never_raises(monkeypatch):
14
- import webbrowser
15
- def boom(*a, **k):
16
- raise RuntimeError("no display")
17
- monkeypatch.setattr(webbrowser, "open", boom)
18
- assert urlopen.open_url("https://x") == "https://x"
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
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
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