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.
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/PKG-INFO +17 -10
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/README.md +16 -9
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/__init__.py +1 -1
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/update.py +36 -7
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_update.py +29 -9
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/.github/workflows/release.yml +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/.gitignore +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/LICENSE +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/NOTICE +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/install.sh +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/pyproject.toml +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/app_tui.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/banner.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/cli.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/commands.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/config.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/conversation.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/diagnostics.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/doctor.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/lsp.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/mcp_client.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/orchestrator.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/planparse.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/prompt.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/__init__.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/anthropic.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/base.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/google.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/ollama.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/providers/openai_compat.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/rawkey.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/registry.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/repomap.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/session.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/symbols.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/theme.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/tools.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/toolspec.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/tui.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/src/two_b/uninstall.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/spike_ctrl_b.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_default_model.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_diagnostics.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_doctor.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_edit_file.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_lsp.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_mcp_resolver.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_search_semantics.py +0 -0
- {2b_agent-0.2.2 → 2b_agent-0.2.3}/tests/test_uninstall.py +0 -0
- {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.
|
|
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?
|
|
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
|
-
|
|
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
|
-
|
|
192
|
+
One command, whatever you installed with — it detects the method and runs the right upgrade:
|
|
192
193
|
|
|
193
194
|
```bash
|
|
194
|
-
2b --update
|
|
195
|
+
2b --update
|
|
195
196
|
```
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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?
|
|
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
|
-
|
|
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
|
-
|
|
178
|
+
One command, whatever you installed with — it detects the method and runs the right upgrade:
|
|
178
179
|
|
|
179
180
|
```bash
|
|
180
|
-
2b --update
|
|
181
|
+
2b --update
|
|
181
182
|
```
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
|
@@ -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
|
|
104
|
-
|
|
105
|
-
if
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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(
|
|
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
|
|
74
|
-
self._patch(update
|
|
75
|
-
|
|
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
|
|
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
|