2b-agent 0.2.2__tar.gz → 0.2.3__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 (50) hide show
  1. {2b_agent-0.2.2 → 2b_agent-0.2.3}/PKG-INFO +17 -10
  2. {2b_agent-0.2.2 → 2b_agent-0.2.3}/README.md +16 -9
  3. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/__init__.py +1 -1
  4. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/update.py +36 -7
  5. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_update.py +29 -9
  6. {2b_agent-0.2.2 → 2b_agent-0.2.3}/.github/workflows/release.yml +0 -0
  7. {2b_agent-0.2.2 → 2b_agent-0.2.3}/.gitignore +0 -0
  8. {2b_agent-0.2.2 → 2b_agent-0.2.3}/LICENSE +0 -0
  9. {2b_agent-0.2.2 → 2b_agent-0.2.3}/NOTICE +0 -0
  10. {2b_agent-0.2.2 → 2b_agent-0.2.3}/install.sh +0 -0
  11. {2b_agent-0.2.2 → 2b_agent-0.2.3}/pyproject.toml +0 -0
  12. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/app_tui.py +0 -0
  13. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/banner.py +0 -0
  14. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/cli.py +0 -0
  15. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/commands.py +0 -0
  16. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/config.py +0 -0
  17. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/conversation.py +0 -0
  18. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/diagnostics.py +0 -0
  19. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/doctor.py +0 -0
  20. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/lsp.py +0 -0
  21. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/mcp_client.py +0 -0
  22. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/orchestrator.py +0 -0
  23. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/planparse.py +0 -0
  24. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/prompt.py +0 -0
  25. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/__init__.py +0 -0
  26. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/anthropic.py +0 -0
  27. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/base.py +0 -0
  28. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/google.py +0 -0
  29. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/ollama.py +0 -0
  30. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/openai_compat.py +0 -0
  31. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/rawkey.py +0 -0
  32. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/registry.py +0 -0
  33. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/repomap.py +0 -0
  34. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/session.py +0 -0
  35. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/symbols.py +0 -0
  36. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/theme.py +0 -0
  37. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/tools.py +0 -0
  38. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/toolspec.py +0 -0
  39. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/tui.py +0 -0
  40. {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/uninstall.py +0 -0
  41. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/spike_ctrl_b.py +0 -0
  42. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_default_model.py +0 -0
  43. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_diagnostics.py +0 -0
  44. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_doctor.py +0 -0
  45. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_edit_file.py +0 -0
  46. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_lsp.py +0 -0
  47. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_mcp_resolver.py +0 -0
  48. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_search_semantics.py +0 -0
  49. {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_uninstall.py +0 -0
  50. {2b_agent-0.2.2 → 2b_agent-0.2.3}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: 2b-agent
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: A local-first coding agent that keeps small local models focused instead of hallucinating.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -157,10 +157,11 @@ Already have Ollama and some models? It skips what you already have — it lists
157
157
  models, offers to just use them (pulling nothing), and marks anything in the menu you've already
158
158
  got. Your existing setup is left untouched.
159
159
 
160
- Prefer to do it by hand? If you already have `uv`:
160
+ Prefer to do it by hand? Install the published package from
161
+ [PyPI](https://pypi.org/project/2b-agent/):
161
162
 
162
163
  ```bash
163
- uv tool install git+https://github.com/dea6cat/2b-agent
164
+ pip install 2b-agent # latest release from PyPI
164
165
  ollama pull qwen3.5:9b # my default — a good balance on an 18 GB machine
165
166
  ```
166
167
 
@@ -188,17 +189,23 @@ Then just type what you want done. Type `/` to see the commands.
188
189
 
189
190
  ### Updating
190
191
 
191
- 2B installs as a `uv` tool, so updating is one command:
192
+ One command, whatever you installed with it detects the method and runs the right upgrade:
192
193
 
193
194
  ```bash
194
- 2b --update # or: uv tool upgrade 2b-agent
195
+ 2b --update
195
196
  ```
196
197
 
197
- Re-running the installer (`curl | sh`) does the same. 2B also checks for a newer
198
- release in the background (at most once a day, never blocking startup) and prints a
199
- one-line notice next launch when one is available set `TWOB_NO_UPDATE_CHECK=1` to
200
- turn that off. Releases are tagged `vMAJOR.MINOR.PATCH`; pin one for reproducibility
201
- with `uv tool install git+https://github.com/dea6cat/2b-agent@v0.2.0`.
198
+ That resolves to `uv tool upgrade 2b-agent` (the `curl | sh` installer / `uv`),
199
+ `pipx upgrade 2b-agent` (pipx), or `pip install -U 2b-agent` (pip). You can of course
200
+ run the matching command yourself e.g. **if you installed with pip**:
201
+
202
+ ```bash
203
+ pip install -U 2b-agent
204
+ ```
205
+
206
+ 2B also checks for a newer release in the background (at most once a day, never blocking
207
+ startup) and prints a one-line notice on the next launch when one is available — set
208
+ `TWOB_NO_UPDATE_CHECK=1` to turn that off. Releases are tagged `vMAJOR.MINOR.PATCH`.
202
209
 
203
210
  ### Providers
204
211
 
@@ -143,10 +143,11 @@ Already have Ollama and some models? It skips what you already have — it lists
143
143
  models, offers to just use them (pulling nothing), and marks anything in the menu you've already
144
144
  got. Your existing setup is left untouched.
145
145
 
146
- Prefer to do it by hand? If you already have `uv`:
146
+ Prefer to do it by hand? Install the published package from
147
+ [PyPI](https://pypi.org/project/2b-agent/):
147
148
 
148
149
  ```bash
149
- uv tool install git+https://github.com/dea6cat/2b-agent
150
+ pip install 2b-agent # latest release from PyPI
150
151
  ollama pull qwen3.5:9b # my default — a good balance on an 18 GB machine
151
152
  ```
152
153
 
@@ -174,17 +175,23 @@ Then just type what you want done. Type `/` to see the commands.
174
175
 
175
176
  ### Updating
176
177
 
177
- 2B installs as a `uv` tool, so updating is one command:
178
+ One command, whatever you installed with it detects the method and runs the right upgrade:
178
179
 
179
180
  ```bash
180
- 2b --update # or: uv tool upgrade 2b-agent
181
+ 2b --update
181
182
  ```
182
183
 
183
- Re-running the installer (`curl | sh`) does the same. 2B also checks for a newer
184
- release in the background (at most once a day, never blocking startup) and prints a
185
- one-line notice next launch when one is available set `TWOB_NO_UPDATE_CHECK=1` to
186
- turn that off. Releases are tagged `vMAJOR.MINOR.PATCH`; pin one for reproducibility
187
- with `uv tool install git+https://github.com/dea6cat/2b-agent@v0.2.0`.
184
+ That resolves to `uv tool upgrade 2b-agent` (the `curl | sh` installer / `uv`),
185
+ `pipx upgrade 2b-agent` (pipx), or `pip install -U 2b-agent` (pip). You can of course
186
+ run the matching command yourself e.g. **if you installed with pip**:
187
+
188
+ ```bash
189
+ pip install -U 2b-agent
190
+ ```
191
+
192
+ 2B also checks for a newer release in the background (at most once a day, never blocking
193
+ startup) and prints a one-line notice on the next launch when one is available — set
194
+ `TWOB_NO_UPDATE_CHECK=1` to turn that off. Releases are tagged `vMAJOR.MINOR.PATCH`.
188
195
 
189
196
  ### Providers
190
197
 
@@ -5,4 +5,4 @@ host keeps the model's world simple — a small, native tool schema over the
5
5
  provider's own wire format, with all orchestration complexity kept host-side.
6
6
  """
7
7
 
8
- __version__ = "0.2.2"
8
+ __version__ = "0.2.3"
@@ -15,6 +15,7 @@ import json
15
15
  import os
16
16
  import shutil
17
17
  import subprocess
18
+ import sys
18
19
  import threading
19
20
  import time
20
21
  import urllib.request
@@ -99,15 +100,43 @@ def notice(now: float | None = None) -> str | None:
99
100
  return msg
100
101
 
101
102
 
103
+ def _kind_from(paths: str) -> str:
104
+ """Classify an install from where its files live: uv tool, pipx, or plain pip."""
105
+ p = paths.replace(os.sep, "/").lower()
106
+ if "/uv/tools/" in p:
107
+ return "uv"
108
+ if "/pipx/" in p:
109
+ return "pipx"
110
+ return "pip"
111
+
112
+
113
+ def _install_kind() -> str:
114
+ """How this 2b-agent was installed, inferred from its run location."""
115
+ return _kind_from(sys.prefix + "|" + os.path.abspath(__file__))
116
+
117
+
102
118
  def run_upgrade(emit) -> int:
103
- """`2b --update`: upgrade the installed tool via uv. Returns uv's exit code (1 if uv
104
- is unavailable). Lets uv's own progress print to the terminal."""
105
- if not shutil.which("uv"):
106
- emit(f"uv not found — update with your installer, e.g. 'uv tool upgrade {PKG}'.")
107
- return 1
108
- emit(f"Updating {PKG} via uv")
119
+ """`2b --update`: upgrade using whatever installed it `uv tool upgrade` (installer/
120
+ uv), `pipx upgrade` (pipx), or `pip install -U` (pip). Returns the tool's exit code
121
+ (1 if the needed tool isn't found). Lets the tool's own progress print to the terminal."""
122
+ kind = _install_kind()
123
+ if kind == "uv":
124
+ if not shutil.which("uv"):
125
+ emit(f"uv not found — run 'uv tool upgrade {PKG}' once it's on PATH.")
126
+ return 1
127
+ emit(f"Updating {PKG} via uv tool…")
128
+ cmd = ["uv", "tool", "upgrade", PKG]
129
+ elif kind == "pipx":
130
+ if not shutil.which("pipx"):
131
+ emit(f"pipx not found — run 'pipx upgrade {PKG}' once it's on PATH.")
132
+ return 1
133
+ emit(f"Updating {PKG} via pipx…")
134
+ cmd = ["pipx", "upgrade", PKG]
135
+ else:
136
+ emit(f"Updating {PKG} via pip…")
137
+ cmd = [sys.executable, "-m", "pip", "install", "-U", PKG]
109
138
  try:
110
- return subprocess.run(["uv", "tool", "upgrade", PKG], timeout=300).returncode
139
+ return subprocess.run(cmd, timeout=600).returncode
111
140
  except Exception as e:
112
141
  emit(f"update failed: {e}")
113
142
  return 1
@@ -64,28 +64,48 @@ class Notice(unittest.TestCase):
64
64
  self.assertIsNone(update.notice(now=self.now))
65
65
 
66
66
 
67
+ class InstallKind(unittest.TestCase):
68
+ def test_kind_from_path(self):
69
+ self.assertEqual(update._kind_from("/home/u/.local/share/uv/tools/2b-agent/lib"), "uv")
70
+ self.assertEqual(update._kind_from("/home/u/.local/pipx/venvs/2b-agent"), "pipx")
71
+ self.assertEqual(update._kind_from("/usr/lib/python3.12/site-packages"), "pip")
72
+
73
+
67
74
  class RunUpgrade(unittest.TestCase):
68
75
  def _patch(self, obj, attr, val):
69
76
  orig = getattr(obj, attr)
70
77
  setattr(obj, attr, val)
71
78
  self.addCleanup(setattr, obj, attr, orig)
72
79
 
73
- def test_uv_absent_returns_1(self):
74
- self._patch(update.shutil, "which", lambda n: None)
75
- out = []
76
- code = update.run_upgrade(out.append)
77
- self.assertEqual(code, 1)
78
- self.assertIn("uv not found", "\n".join(out))
79
-
80
- def test_uv_present_invokes_upgrade(self):
81
- self._patch(update.shutil, "which", lambda n: "/usr/bin/uv")
80
+ def _capture(self, kind, which_ok=True):
81
+ self._patch(update, "_install_kind", lambda: kind)
82
+ self._patch(update.shutil, "which", lambda n: "/usr/bin/" + n if which_ok else None)
82
83
  calls = []
83
84
  self._patch(update.subprocess, "run",
84
85
  lambda argv, **kw: calls.append(argv) or types.SimpleNamespace(returncode=0))
85
86
  code = update.run_upgrade([].append)
87
+ return code, calls
88
+
89
+ def test_uv_install_uses_uv_tool(self):
90
+ code, calls = self._capture("uv")
86
91
  self.assertEqual(code, 0)
87
92
  self.assertIn(["uv", "tool", "upgrade", "2b-agent"], calls)
88
93
 
94
+ def test_pipx_install_uses_pipx(self):
95
+ code, calls = self._capture("pipx")
96
+ self.assertEqual(code, 0)
97
+ self.assertIn(["pipx", "upgrade", "2b-agent"], calls)
98
+
99
+ def test_pip_install_uses_pip(self):
100
+ code, calls = self._capture("pip")
101
+ self.assertEqual(code, 0)
102
+ self.assertEqual(calls[0][1:], ["-m", "pip", "install", "-U", "2b-agent"]) # sys.executable -m pip …
103
+
104
+ def test_uv_absent_returns_1(self):
105
+ code, calls = self._capture("uv", which_ok=False)
106
+ self.assertEqual(code, 1)
107
+ self.assertEqual(calls, [])
108
+
89
109
 
90
110
  if __name__ == "__main__":
91
111
  unittest.main()
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