2b-agent 0.2.1__py3-none-any.whl

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.
@@ -0,0 +1,336 @@
1
+ Metadata-Version: 2.4
2
+ Name: 2b-agent
3
+ Version: 0.2.1
4
+ Summary: A local-first coding agent that keeps small local models focused instead of hallucinating.
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ License-File: NOTICE
8
+ Requires-Python: >=3.11
9
+ Requires-Dist: mcp>=1.0
10
+ Requires-Dist: prompt-toolkit>=3.0
11
+ Requires-Dist: rich>=13.0
12
+ Requires-Dist: textual>=0.60
13
+ Description-Content-Type: text/markdown
14
+
15
+ # 2B
16
+
17
+ A local-first coding agent for the terminal. It runs your own models over Ollama's
18
+ native protocol, keeps them focused instead of hallucinating, and gives you a full-screen
19
+ TUI — streaming replies, a live plan checklist, narrated tool actions — without ever routing
20
+ your local model through a translation layer that would confuse it.
21
+
22
+ I named it **2B**, after NieR: Automata. It's built to keep working when the power and the
23
+ internet don't — I live somewhere the grid isn't a guarantee, and I wanted a coding agent that
24
+ doesn't fall apart the moment I'm offline.
25
+
26
+ > **macOS only.** 2B is built and tested for macOS — the installer is a shell script that leans on
27
+ > Homebrew, and the clipboard integration uses `pbcopy`. It hasn't been tested elsewhere.
28
+
29
+ ---
30
+
31
+ ## Why I built it
32
+
33
+ I kept being told that small models — Nemotron 3 Nano 4B, `gpt-oss:20b`, the Qwen family — were
34
+ "good enough" for agentic coding. On paper they were. In practice, every off-the-shelf harness I
35
+ tried broke them:
36
+
37
+ - **opencode** made `gpt-oss` invent tool names that didn't exist and emit fake `<command>` tags as
38
+ plain text.
39
+ - **Cline**, **Goose**, **Continue**, **OpenHands** each failed in their own way — malformed tool
40
+ schemas, reasoning collapse, the model narrating tool calls instead of making them.
41
+
42
+ The models weren't the problem. The harnesses were. Nearly all of them talk to a local model
43
+ through a generic **OpenAI-compatible `/v1` shim** and pile abstraction on top of it. That shim
44
+ measurably degrades a small model's tool selection, and the extra complexity buries whatever
45
+ capability the model actually has.
46
+
47
+ The one thing that worked cleanly was a ~350-line script I wrote that talked to Ollama's **native
48
+ `/api/chat`** endpoint with a tiny, fixed set of five tools. So I grew that prototype into a real,
49
+ shareable tool. That's 2B.
50
+
51
+ **The core rule, and the whole point:** all complexity lives on the *host* side. The model's world
52
+ never changes — the same five tools, the same native wire format for whatever provider is active, no
53
+ generic shim. Everything you see below — the TUI, the plan checklist, task management, model
54
+ switching, auto-compaction — is something 2B renders *around* the tool loop, never a new thing the
55
+ model has to understand.
56
+
57
+ ---
58
+
59
+ ## What it does
60
+
61
+ - **Five tools, and only five.** `list_files`, `read_file`, `search_files`, `edit_file`,
62
+ `write_file`. That small, concrete surface is exactly what keeps a small model reliable. It
63
+ explores before it edits — searching for where something lives instead of guessing paths — and
64
+ prefers exact-snippet edits over rewriting whole files.
65
+ - **Edits that survive small-model drift.** `edit_file` resolves the target host-side in tiers —
66
+ exact, then whitespace-tolerant, then indentation-agnostic (re-indenting your snippet to the
67
+ file) — so a model that gets the whitespace slightly wrong still lands the edit instead of
68
+ bouncing off an exact-match error. It never applies on an ambiguous match, and the tool the model
69
+ sees is unchanged — all the tolerance lives on the host.
70
+ - **Catches its own mistakes.** After a successful edit, 2B runs the file's checker host-side
71
+ (`dart analyze`, `ruff` or `py_compile`, …) and folds any new errors straight into the tool
72
+ result — so a model that just broke the build sees it on the same turn, with no new tool to learn.
73
+ Bounded so it can't flood a small window, skipped silently when there's no checker, and off with
74
+ `TWOB_NO_DIAGNOSTICS`.
75
+ - **Finds definitions, not just matches.** `search_files` marks which hit is the *definition* of a
76
+ symbol and floats it to the top, and `read_file` appends a compact symbol outline with line
77
+ anchors — so "where is X defined?" is answered by the tools the model already calls, with no
78
+ navigation tool to learn. When a language server is installed (`dart language-server`, `pyright`,
79
+ `gopls`, …) it resolves symbols semantically over LSP, spoken as raw stdlib JSON-RPC; a curated MCP
80
+ resolver is used if one's enabled; with neither, it falls back to a dependency-free regex map.
81
+ Host-side, schema unchanged; off with `TWOB_NO_LSP`.
82
+ - **Native protocols, never a shim.** Local Ollama models get Ollama's own `/api/chat` with NDJSON
83
+ streaming. Each cloud provider gets its own native format. Nothing is translated through a
84
+ lowest-common-denominator layer.
85
+ - **Streaming, full-screen TUI.** A scrolling conversation, a framed input box, a live status line
86
+ with a spinner, elapsed time, and — for local models — a RAM/GPU readout pulled from Ollama.
87
+ - **Narrated tool actions.** Instead of a wall of raw `read_file {...}`, you see what it's doing in
88
+ plain language, tied together with a tree gutter and a ✓/✗ per step:
89
+ ```
90
+ ├ ✓ Searching for "MemoryScopeLevel" in lib
91
+ ├ ✓ Reading lib/memory/memory_store.dart
92
+ └ ✓ Editing lib/memory/memory_store.dart
93
+ ```
94
+ - **A live plan checklist.** The model writes a short numbered plan before its first tool call; 2B
95
+ parses it and renders it as a checklist that advances (`□` pending, `■` active, `✓` done) as the
96
+ work progresses. Purely cosmetic — a wrong guess never breaks anything.
97
+ - **Many providers, one conversation.** Ollama (local and cloud), OpenAI, OpenRouter, Mistral,
98
+ NVIDIA, Anthropic, and Google Gemini. 2B keeps history in a provider-agnostic form and
99
+ re-serializes it fresh for whoever's active — so you can switch models *mid-task* with `/model`
100
+ and keep every bit of context. Start a task on a local Qwen, hand it to Claude when it gets hard,
101
+ keep going.
102
+ - **Knows the project.** `/init` scans the repo and writes a compact `2B.md` — stack, layout, and a
103
+ ranked symbol map — that's auto-loaded into context, so the model starts knowing where things are
104
+ instead of hunting for files. `/map` shows a budget-bounded outline on demand. All bounded, so it
105
+ never floods a small local window.
106
+ - **Runs things — split by model.** Local models get `run_git` (git only, never a raw shell — no
107
+ chaining/injection); cloud models get a full `run_command` shell (tests, build, git). Read-only git
108
+ runs freely; anything that mutates is confirmation-gated and refused in plan mode.
109
+ - **MCP tools, curated.** Pull in tools from MCP servers (dart, mempalace, …) — but **per tool**, not
110
+ wholesale, because flooding a small model with tools is exactly what breaks it. You enable a server
111
+ and pick which of its tools the model sees (`/mcp`); local models are capped to a few so their
112
+ context stays lean. See [MCP servers](#mcp-servers-extra-tools).
113
+ - **Operating modes**, cycled with **Shift+Tab** or set with `/mode`:
114
+ - **normal** — every write/edit asks first.
115
+ - **accept edits** — writes apply automatically.
116
+ - **plan mode** — read-only; `edit_file`/`write_file` *and* MCP tools are refused (they may change
117
+ state), so the model investigates and returns a plan instead of touching anything.
118
+ - **Auto-compaction.** When a conversation nears the model's context window — which happens fast on
119
+ small local windows — 2B folds the older turns into a summary and keeps going uninterrupted,
120
+ instead of hitting the wall. It cuts on a safe boundary so nothing breaks, and shows you
121
+ "Compacting conversation…" while it does it.
122
+ - **Themes.** `/theme system` (default — transparent, uses your terminal's own background),
123
+ `/theme light` (a warm parchment palette), `/theme dark` (a dimmed version). Switches live.
124
+ - **Copy that actually works.** Drag to select any text and press **Ctrl+C**, or **Ctrl+Y** / `/copy`
125
+ to grab the whole last reply. On macOS this goes through `pbcopy`, so it lands on your clipboard
126
+ even in Terminal.app (which ignores the escape sequence most TUIs rely on).
127
+ - **Multiple tasks.** Queue tasks, background the running one with **Ctrl+B**, foreground it later
128
+ with `/fg`. A backgrounded task pauses when it needs to write and waits for you.
129
+ - **Undo.** `/undo` reverts the last write or edit — one level, but it's there.
130
+
131
+ ---
132
+
133
+ ## Install
134
+
135
+ One line — paste it in your terminal:
136
+
137
+ ```bash
138
+ curl -fsSL https://raw.githubusercontent.com/dea6cat/2b-agent/main/install.sh | sh
139
+ ```
140
+
141
+ It installs [`uv`](https://docs.astral.sh/uv/) if you don't have it, installs the `2b` command,
142
+ then — on an interactive terminal — walks you through local model setup:
143
+
144
+ 1. **Optional clean install** — offers to remove other agentic tools that proved unreliable with
145
+ local models (opencode, Continue, Goose, Cline, OpenHands) and their configs. Off by default;
146
+ it asks first.
147
+ 2. **Grades your machine** — reads your RAM and chip and rates each candidate model
148
+ (`✓ fits well` / `~ tight` / `✗ needs NGB+`), defaulting to the best one your hardware can run.
149
+ 3. **You pick** one or several from the menu.
150
+ 4. **Installs Ollama and pulls** what you chose, with a live progress bar.
151
+ 5. **Self-tests** each model — tok/s + GPU residency, then a **correctness check that runs a real
152
+ one-line edit through 2B itself** and verifies the result (`✓ correct` / `✗ wrong`, ~20–90s per
153
+ model). It only reports — it never removes a model — and `--no-benchmark` skips it. Then it prints
154
+ how to launch 2B.
155
+
156
+ Already have Ollama and some models? It skips what you already have — it lists your installed
157
+ models, offers to just use them (pulling nothing), and marks anything in the menu you've already
158
+ got. Your existing setup is left untouched.
159
+
160
+ Prefer to do it by hand? If you already have `uv`:
161
+
162
+ ```bash
163
+ uv tool install git+https://github.com/dea6cat/2b-agent
164
+ ollama pull qwen3.5:9b # my default — a good balance on an 18 GB machine
165
+ ```
166
+
167
+ The installer is scriptable too: `--yes` (accept defaults, no prompts), `--clean` / `--no-clean`,
168
+ `--models "qwen3.5:9b qwen3:8b"`, `--no-models`, `--no-benchmark` (skip the correctness check),
169
+ `--fix-path` / `--no-fix-path` (add uv's tool dir to your PATH for you via `uv tool update-shell`,
170
+ or leave it — otherwise it asks, and never edits a profile without consent). Pass them through the
171
+ pipe with `... | sh -s -- --yes --models "qwen3.5:9b"`.
172
+
173
+ ---
174
+
175
+ ## Use it
176
+
177
+ ```bash
178
+ 2b # start in the current directory, autodetects a local model
179
+ 2b "add a docstring to lib/main.dart" # run one task, then drop into the session
180
+ 2b --model qwen3.5:9b # pin a model
181
+ 2b --list-models # what's available across configured providers
182
+ 2b --doctor # diagnose PATH, Ollama, and the default model, then exit
183
+ 2b --update # upgrade to the latest release (uv tool upgrade)
184
+ 2b --rm # uninstall 2B and delete its config (asks first); --rm --yes to skip
185
+ ```
186
+
187
+ Then just type what you want done. Type `/` to see the commands.
188
+
189
+ ### Updating
190
+
191
+ 2B installs as a `uv` tool, so updating is one command:
192
+
193
+ ```bash
194
+ 2b --update # or: uv tool upgrade 2b-agent
195
+ ```
196
+
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`.
202
+
203
+ ### Providers
204
+
205
+ Local Ollama needs nothing. For anything else, set the matching environment variable and it shows
206
+ up automatically in `/models`:
207
+
208
+ | Provider | Environment variable |
209
+ | ---------- | ------------------------------------------- |
210
+ | Ollama | `OLLAMA_API_BASE` (or `OLLAMA_HOST`) |
211
+ | Ollama Cloud | `OLLAMA_API_KEY` |
212
+ | OpenAI | `OPENAI_API_KEY` |
213
+ | OpenRouter | `OPENROUTER_API_KEY` |
214
+ | Mistral | `MISTRAL_API_KEY` |
215
+ | NVIDIA | `NVIDIA_API_KEY` |
216
+ | Anthropic | `ANTHROPIC_API_KEY` |
217
+ | Google | `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) |
218
+
219
+ Or connect one from **inside** 2B — `/connect <provider>` prompts for the key with a hidden field
220
+ and saves it to `~/.config/2b/keys.json` (`chmod 600`) so it's there next time; `/connect` on its own
221
+ shows what's connected, and `/disconnect <provider>` removes a saved key. A key exported in your
222
+ shell always takes precedence over a saved one.
223
+
224
+ Switch models anytime with `/model <name>`. A bare name works when it's unambiguous; otherwise use
225
+ `provider:model` (e.g. `/model anthropic:claude-sonnet-5`).
226
+
227
+ ### Commands
228
+
229
+ | Command | What it does |
230
+ | --- | --- |
231
+ | `/help` | List commands |
232
+ | `/model [name]` | Show or switch model — context is preserved |
233
+ | `/models [filter]` | List available models, grouped by provider |
234
+ | `/connect [provider] [key]` | Connect a provider (hidden key prompt); bare shows status |
235
+ | `/disconnect <provider>` | Remove a saved provider key |
236
+ | `/init` | Scan the project → write `2B.md` (a compact map auto-loaded into context on new tasks) |
237
+ | `/map [subdir]` | Show a budget-bounded symbol outline of the project |
238
+ | `/mcp` | MCP servers/tools: status, `tools <server>`, `enable`/`disable <server> <tool…\|all>` |
239
+ | `/mode [normal\|accept\|plan]` | Set operating mode (or **Shift+Tab** to cycle) |
240
+ | `/theme [system\|light\|dark]` | Switch color theme |
241
+ | `/context` | Show estimated context usage (auto-compacts near the limit) |
242
+ | `/copy` | Copy the last reply to the clipboard (**Ctrl+Y**) |
243
+ | `/task <desc>` | Queue a task |
244
+ | `/tasks` | List tasks and their status |
245
+ | `/fg <id>` | Foreground a backgrounded task |
246
+ | `/yes` | Toggle accept-edits mode |
247
+ | `/undo` | Revert the last write/edit |
248
+ | `/diff` | Re-show the last diff |
249
+ | `/add <file>` | Pre-load a file into the current task's context |
250
+ | `/clear` | Reset the current task's history |
251
+ | `/quit` | Exit |
252
+
253
+ ### Keyboard
254
+
255
+ | Key | Action |
256
+ | --- | --- |
257
+ | **Shift+Tab** | Cycle operating mode |
258
+ | **Ctrl+B** | Background the running task |
259
+ | **Ctrl+Y** | Copy the last reply |
260
+ | **Ctrl+C** | Copy the current mouse selection |
261
+ | **Esc** | Stop the current stream/task immediately — back to idle |
262
+ | **Ctrl+D** | Quit |
263
+ | **Tab** | Accept the top `/`-command suggestion |
264
+
265
+ ### MCP servers (extra tools)
266
+
267
+ 2B can pull in tools from [MCP](https://modelcontextprotocol.io) servers (stdio) like `dart` or
268
+ `mempalace`. But its whole reason for existing is that **small local models break when you flood them
269
+ with tools** — so MCP tools are opt-in and **curated per tool**: you enable a server and pick exactly
270
+ which of its tools reach the model. Nothing is exposed until you say so.
271
+
272
+ Declare servers the usual way — a Claude-Code-style `mcpServers` block in `~/.config/2b/mcp.json` (or
273
+ `./.mcp.json` in a project, which wins):
274
+
275
+ ```json
276
+ {
277
+ "mcpServers": {
278
+ "dart": { "command": "dart", "args": ["mcp-server"] }
279
+ }
280
+ }
281
+ ```
282
+
283
+ Then curate from inside 2B:
284
+
285
+ ```
286
+ /mcp # servers and how many tools each has enabled
287
+ /mcp tools dart # list a server's tools ([x] = enabled)
288
+ /mcp enable dart hot_reload analyze_files
289
+ /mcp enable dart all # expose everything (careful on small models)
290
+ /mcp disable dart hot_reload # or /mcp disable dart to turn the whole server off
291
+ ```
292
+
293
+ Enabled tools appear to the model as `server__tool` (e.g. `dart__hot_reload`) and their results come
294
+ straight back into the loop. Only the tools you enable are ever sent — the model's tool list stays as
295
+ small as you keep it.
296
+
297
+ ### Configuration
298
+
299
+ - **Context window (local) — sized to your machine.** For a local model 2B works out the largest
300
+ window your box can run *comfortably* and pins `num_ctx` to it on every request (Ollama otherwise
301
+ defaults to ~4k regardless of the model). It reads the model's architecture and trained max from
302
+ `/api/show`, computes the KV-cache cost per token, and fits it into the RAM left after the model
303
+ weights plus a headroom reserve — so a 16 GB laptop, an 18 GB one, and a 64 GB workstation each get
304
+ a different, appropriate window (e.g. qwen3.5:9b ≈ 13k on 18 GB), never more than the model was
305
+ trained for. That number drives auto-compaction (~75%) and the read-a-section threshold. Set
306
+ `TWOB_CONTEXT_TOKENS` to override (higher if you want to spend more RAM, lower to save it).
307
+
308
+ ---
309
+
310
+ ## Honest caveats
311
+
312
+ - **It reads and writes wherever you point it.** 2B resolves absolute paths and paths outside the
313
+ working directory on purpose — it's a personal tool for your machine. Writes are still confirmed
314
+ in normal mode, and plan mode refuses them entirely.
315
+ - **Switching to a stronger model mid-task hands it a tool-call history it didn't make.** For these
316
+ five simple tools that's low-risk (the wire format is unambiguously the new provider's own; only
317
+ the *choices* inside came from a weaker model), but you may see mild "why did I read that file"
318
+ moments. It is *not* the shim-degradation failure that sank the other harnesses.
319
+ - **It's a full-screen TUI.** That means it lives in the terminal's alternate screen, so a single
320
+ mouse drag selects what's on screen, not scrolled-off history. For a classic inline REPL, run
321
+ `2b --classic`.
322
+
323
+ ---
324
+
325
+ ## How it's built
326
+
327
+ - **Python, standard library first.** The only real dependencies are `rich`, `prompt_toolkit`, and
328
+ `textual`. Every provider talks over `urllib` — no per-provider SDKs.
329
+ - **A canonical conversation model** that each provider serializes fresh on every request. That
330
+ re-derivation is exactly what makes switching models mid-task safe.
331
+ - **One worker thread per task**, emitting events into a queue the UI thread drains and renders — so
332
+ the tool code stays untouched off the main thread and one thread owns the terminal.
333
+ - The five-tool schema is **frozen**. It's what makes small models reliable, and it isn't up for
334
+ redesign.
335
+
336
+ Built for local models, kept on task.
@@ -0,0 +1,37 @@
1
+ two_b/__init__.py,sha256=96B1rq3lVOOO5gzdnwiap9l_m2TlXeOIJXoQT5rFAoE,298
2
+ two_b/app_tui.py,sha256=Xmr_4qqi4qKBw9Y0atrBQRkUmsyWK4A7APIypvpOD18,30279
3
+ two_b/banner.py,sha256=BZSKd0A80yOWPjaxUT4rmG_4Nv-SHQWciTVTRd2ZYzk,1788
4
+ two_b/cli.py,sha256=W9tNZz5kvO-VG9SSLYZpdd4fLxdLI4YPBfbm2Ry9lQY,14273
5
+ two_b/commands.py,sha256=pvNWT1fwjjBhQ0HdgSjJefAGEFIHplHpN-gS9yw95Hk,20127
6
+ two_b/config.py,sha256=2f_GQaatWe487AhmQB99BprZxtoUi_H-SJOPQiqh9SQ,3503
7
+ two_b/conversation.py,sha256=g5IfPfdnSoeRhTHpyhX_TH9NDTYRMVHjyw0DraHPxKA,3139
8
+ two_b/diagnostics.py,sha256=HKnLNCyQkYBguwxZ_rD5CO4Q0Gb_hZpxpsfQpClXAOs,4891
9
+ two_b/doctor.py,sha256=rAqLgArM32sSe1rcW36_PA5h8b7KjTX9RaFUfQ1Eums,3145
10
+ two_b/lsp.py,sha256=w8RV2PFzeEHCvztfdXn0Lc5v-Vxtd754yc8ndWxs2Eg,10136
11
+ two_b/mcp_client.py,sha256=eunqWuMK6rXMSeRhQ0ozmTBLS0P6t-fVCgrmv4oT5pY,10432
12
+ two_b/orchestrator.py,sha256=icc9uPc_932vucS1vM74UCsj8Is-Cf5CJBIm4z4sC0A,26229
13
+ two_b/planparse.py,sha256=0PJ-br9XMSb2Q80jnkiH9gjaLPpEhJcpx4AOdmunnxQ,3510
14
+ two_b/prompt.py,sha256=ebyeGcbnZu4nWpOP6k8q49hAG_8ljpMMq81Ph9Z4WX4,3230
15
+ two_b/rawkey.py,sha256=ctMD5GSrjfFCLgW_R_JUVMzsB8sLEI7m-P6DnzJFkQ4,4293
16
+ two_b/registry.py,sha256=mreiG2FEu7L-k5J_1hCDu5WjRHy-iVDyk_GfiJIv5PY,3130
17
+ two_b/repomap.py,sha256=S_s761Yrk-oEqgjCCKw0LXCMRKOlS-gYiXpNr6hsC2Y,7469
18
+ two_b/session.py,sha256=kd6mJ7Ptw1lhazXTbgzOj4uaA7qaGskTN-3feCku9Fg,5749
19
+ two_b/symbols.py,sha256=uEv-w691PA7DvBOFSRpoyVlH0UK0JZduv7XA1umvhdc,4868
20
+ two_b/theme.py,sha256=Frr_RO8UQufN9iNqvxHxHbNYCWvvz-b0YdxSMlEV_lU,2173
21
+ two_b/tools.py,sha256=gNhyMcGvh5A7A5S1O40Lb46G4DkzHaU9JnKPZ1D3iQo,23425
22
+ two_b/toolspec.py,sha256=grDY3L6MdBgZYzGCDr91pSVv8VdgjKYUlx7v_tyFQVo,5695
23
+ two_b/tui.py,sha256=jL1tFNYVqOdupvH4vSwuHmxs0iSlU7dRgb-nWhHvARA,3230
24
+ two_b/uninstall.py,sha256=mui5-3X6DE4L_MaDA0nShIx7Rt4ZIDCRXW8yAwkZd_c,2950
25
+ two_b/update.py,sha256=hl2OURWttr2uAVYrFC8wfhbN-BqDk4hHT_mhBI67qJ4,3895
26
+ two_b/providers/__init__.py,sha256=HZar3QzvMZgFjQBZiEP0H7CS2aXKU8LaiSvgigRt2Dw,157
27
+ two_b/providers/anthropic.py,sha256=z5YGlw3sIzil_Q1kz6oyv-Bqk39ZUjQAz9EWgJhyOHc,3578
28
+ two_b/providers/base.py,sha256=TSxsRsBVAYYDYOQX6FZuupA4Ef_jyDR8O7XPfTjxu1A,3949
29
+ two_b/providers/google.py,sha256=49QOSWPzaYNilZHAkYyzsPEyfo_UW6H64mefx4OvwM4,3609
30
+ two_b/providers/ollama.py,sha256=0GZTN-KW8EW9xJz2jpXQ-3TOTXxlG0E4qEqhHRZawXc,11031
31
+ two_b/providers/openai_compat.py,sha256=c5hJoix7RzJSv8fBAdJKeNqUZeMV602OvC0vrYmIbZg,7054
32
+ 2b_agent-0.2.1.dist-info/METADATA,sha256=B_8u01-1UZFBVHo5wGypVNPyfhhLcQ4BU_qQHVZTAyU,18270
33
+ 2b_agent-0.2.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
34
+ 2b_agent-0.2.1.dist-info/entry_points.txt,sha256=BIXHqUGPSMSwe7tcn3DDbrqcAuJ2XGAhCJvcAHMnbgE,38
35
+ 2b_agent-0.2.1.dist-info/licenses/LICENSE,sha256=wBWiryVo7g0De1zKcDymQ3_ayf3Zih5MJIpDeuzW7fM,11348
36
+ 2b_agent-0.2.1.dist-info/licenses/NOTICE,sha256=xJNked-oA-tWYbsSS4CjUvLZdDkaNOidjWCB1E-KhQw,122
37
+ 2b_agent-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ 2b = two_b.cli:main
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Alexander Montolio
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,4 @@
1
+ 2b-agent
2
+ Copyright 2026 Alexander Montolio
3
+
4
+ This product is licensed under the Apache License, Version 2.0 (see LICENSE).
two_b/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ """2B — a local-first coding agent.
2
+
3
+ Built on one hard-won lesson: small local models stay reliable only when the
4
+ host keeps the model's world simple — a small, native tool schema over the
5
+ provider's own wire format, with all orchestration complexity kept host-side.
6
+ """
7
+
8
+ __version__ = "0.2.1"