banto 5.2.2__tar.gz → 5.2.4__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 (117) hide show
  1. {banto-5.2.2 → banto-5.2.4}/PKG-INFO +17 -11
  2. {banto-5.2.2 → banto-5.2.4}/README.md +15 -10
  3. {banto-5.2.2 → banto-5.2.4}/banto/__init__.py +1 -1
  4. {banto-5.2.2 → banto-5.2.4}/banto/__main__.py +21 -1
  5. {banto-5.2.2 → banto-5.2.4}/banto/broker_client.py +13 -2
  6. {banto-5.2.2 → banto-5.2.4}/banto/broker_setup.py +4 -1
  7. {banto-5.2.2 → banto-5.2.4}/banto.egg-info/PKG-INFO +17 -11
  8. {banto-5.2.2 → banto-5.2.4}/banto.egg-info/SOURCES.txt +1 -0
  9. {banto-5.2.2 → banto-5.2.4}/banto.egg-info/requires.txt +3 -0
  10. {banto-5.2.2 → banto-5.2.4}/pyproject.toml +2 -1
  11. {banto-5.2.2 → banto-5.2.4}/tests/test_broker.py +34 -0
  12. banto-5.2.4/tests/test_cli_version.py +58 -0
  13. {banto-5.2.2 → banto-5.2.4}/LICENSE +0 -0
  14. {banto-5.2.2 → banto-5.2.4}/banto/backend.py +0 -0
  15. {banto-5.2.2 → banto-5.2.4}/banto/broker.py +0 -0
  16. {banto-5.2.2 → banto-5.2.4}/banto/broker_cli.py +0 -0
  17. {banto-5.2.2 → banto-5.2.4}/banto/broker_mcp.py +0 -0
  18. {banto-5.2.2 → banto-5.2.4}/banto/chatgpt.py +0 -0
  19. {banto-5.2.2 → banto-5.2.4}/banto/config.json +0 -0
  20. {banto-5.2.2 → banto-5.2.4}/banto/ecdsa_p256.py +0 -0
  21. {banto-5.2.2 → banto-5.2.4}/banto/guard.py +0 -0
  22. {banto-5.2.2 → banto-5.2.4}/banto/keychain.py +0 -0
  23. {banto-5.2.2 → banto-5.2.4}/banto/kimaru_evaluation.py +0 -0
  24. {banto-5.2.2 → banto-5.2.4}/banto/kimaru_material.py +0 -0
  25. {banto-5.2.2 → banto-5.2.4}/banto/lease.py +0 -0
  26. {banto-5.2.2 → banto-5.2.4}/banto/lease_cli.py +0 -0
  27. {banto-5.2.2 → banto-5.2.4}/banto/mcp_server.py +0 -0
  28. {banto-5.2.2 → banto-5.2.4}/banto/pricing.json +0 -0
  29. {banto-5.2.2 → banto-5.2.4}/banto/profiles.py +0 -0
  30. {banto-5.2.2 → banto-5.2.4}/banto/register_popup.py +0 -0
  31. {banto-5.2.2 → banto-5.2.4}/banto/rotation_capabilities.json +0 -0
  32. {banto-5.2.2 → banto-5.2.4}/banto/sealed_cli.py +0 -0
  33. {banto-5.2.2 → banto-5.2.4}/banto/sealed_signer.py +0 -0
  34. {banto-5.2.2 → banto-5.2.4}/banto/sync/__init__.py +0 -0
  35. {banto-5.2.2 → banto-5.2.4}/banto/sync/audit.py +0 -0
  36. {banto-5.2.2 → banto-5.2.4}/banto/sync/browser_batch.py +0 -0
  37. {banto-5.2.2 → banto-5.2.4}/banto/sync/browser_issuer.py +0 -0
  38. {banto-5.2.2 → banto-5.2.4}/banto/sync/browser_recorder.py +0 -0
  39. {banto-5.2.2 → banto-5.2.4}/banto/sync/capabilities.py +0 -0
  40. {banto-5.2.2 → banto-5.2.4}/banto/sync/cli.py +0 -0
  41. {banto-5.2.2 → banto-5.2.4}/banto/sync/cloudflare_token_rotator.py +0 -0
  42. {banto-5.2.2 → banto-5.2.4}/banto/sync/config.py +0 -0
  43. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/__init__.py +0 -0
  44. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/alibaba.py +0 -0
  45. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/aws_sm.py +0 -0
  46. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/aws_ssm.py +0 -0
  47. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/azure.py +0 -0
  48. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/azure_devops.py +0 -0
  49. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/base.py +0 -0
  50. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/bitbucket.py +0 -0
  51. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/circleci.py +0 -0
  52. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/cloudflare.py +0 -0
  53. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/deno.py +0 -0
  54. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/digitalocean.py +0 -0
  55. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/docker.py +0 -0
  56. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/flyio.py +0 -0
  57. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/gcp.py +0 -0
  58. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/github.py +0 -0
  59. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/gitlab.py +0 -0
  60. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/hasura.py +0 -0
  61. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/heroku.py +0 -0
  62. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/huawei.py +0 -0
  63. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/jdcloud.py +0 -0
  64. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/kubernetes.py +0 -0
  65. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/laravel_forge.py +0 -0
  66. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/local.py +0 -0
  67. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/naver.py +0 -0
  68. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/netlify.py +0 -0
  69. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/nhn.py +0 -0
  70. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/railway.py +0 -0
  71. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/render.py +0 -0
  72. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/sakura.py +0 -0
  73. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/supabase.py +0 -0
  74. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/tencent.py +0 -0
  75. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/terraform.py +0 -0
  76. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/vercel.py +0 -0
  77. {banto-5.2.2 → banto-5.2.4}/banto/sync/drivers/volcengine.py +0 -0
  78. {banto-5.2.2 → banto-5.2.4}/banto/sync/google_rotator.py +0 -0
  79. {banto-5.2.2 → banto-5.2.4}/banto/sync/history.py +0 -0
  80. {banto-5.2.2 → banto-5.2.4}/banto/sync/incident_report.py +0 -0
  81. {banto-5.2.2 → banto-5.2.4}/banto/sync/notifiers/__init__.py +0 -0
  82. {banto-5.2.2 → banto-5.2.4}/banto/sync/notifiers/base.py +0 -0
  83. {banto-5.2.2 → banto-5.2.4}/banto/sync/notifiers/datadog.py +0 -0
  84. {banto-5.2.2 → banto-5.2.4}/banto/sync/notifiers/pagerduty.py +0 -0
  85. {banto-5.2.2 → banto-5.2.4}/banto/sync/notifiers/slack.py +0 -0
  86. {banto-5.2.2 → banto-5.2.4}/banto/sync/notifiers/teams.py +0 -0
  87. {banto-5.2.2 → banto-5.2.4}/banto/sync/openai_rotator.py +0 -0
  88. {banto-5.2.2 → banto-5.2.4}/banto/sync/propagation.py +0 -0
  89. {banto-5.2.2 → banto-5.2.4}/banto/sync/setup.py +0 -0
  90. {banto-5.2.2 → banto-5.2.4}/banto/sync/smoke_presets.py +0 -0
  91. {banto-5.2.2 → banto-5.2.4}/banto/sync/stripe_webhook_rotator.py +0 -0
  92. {banto-5.2.2 → banto-5.2.4}/banto/sync/sync.py +0 -0
  93. {banto-5.2.2 → banto-5.2.4}/banto/sync/sync_state.py +0 -0
  94. {banto-5.2.2 → banto-5.2.4}/banto/sync/validate.py +0 -0
  95. {banto-5.2.2 → banto-5.2.4}/banto/sync/vercel_inventory.py +0 -0
  96. {banto-5.2.2 → banto-5.2.4}/banto/sync/web.py +0 -0
  97. {banto-5.2.2 → banto-5.2.4}/banto/sync/x_oauth_pkce.py +0 -0
  98. {banto-5.2.2 → banto-5.2.4}/banto/sync/x_oauth_rotator.py +0 -0
  99. {banto-5.2.2 → banto-5.2.4}/banto/sync/xai_rotator.py +0 -0
  100. {banto-5.2.2 → banto-5.2.4}/banto/vault.py +0 -0
  101. {banto-5.2.2 → banto-5.2.4}/banto/widgets.py +0 -0
  102. {banto-5.2.2 → banto-5.2.4}/banto.egg-info/dependency_links.txt +0 -0
  103. {banto-5.2.2 → banto-5.2.4}/banto.egg-info/entry_points.txt +0 -0
  104. {banto-5.2.2 → banto-5.2.4}/banto.egg-info/top_level.txt +0 -0
  105. {banto-5.2.2 → banto-5.2.4}/setup.cfg +0 -0
  106. {banto-5.2.2 → banto-5.2.4}/tests/test_broker_setup.py +0 -0
  107. {banto-5.2.2 → banto-5.2.4}/tests/test_ecdsa_p256.py +0 -0
  108. {banto-5.2.2 → banto-5.2.4}/tests/test_hold_timeout.py +0 -0
  109. {banto-5.2.2 → banto-5.2.4}/tests/test_host_validation.py +0 -0
  110. {banto-5.2.2 → banto-5.2.4}/tests/test_keychain_account.py +0 -0
  111. {banto-5.2.2 → banto-5.2.4}/tests/test_kimaru_evaluation.py +0 -0
  112. {banto-5.2.2 → banto-5.2.4}/tests/test_kimaru_material.py +0 -0
  113. {banto-5.2.2 → banto-5.2.4}/tests/test_lease.py +0 -0
  114. {banto-5.2.2 → banto-5.2.4}/tests/test_model_profiles.py +0 -0
  115. {banto-5.2.2 → banto-5.2.4}/tests/test_recommend_profile.py +0 -0
  116. {banto-5.2.2 → banto-5.2.4}/tests/test_register_popup.py +0 -0
  117. {banto-5.2.2 → banto-5.2.4}/tests/test_sealed_signer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: banto
3
- Version: 5.2.2
3
+ Version: 5.2.4
4
4
  Summary: Local-first secret management with optional budget gating and dynamic leases
5
5
  Author: AllNew LLC
6
6
  License-Expression: LicenseRef-Dual
@@ -20,6 +20,7 @@ Classifier: Topic :: Software Development :: Libraries
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
+ Requires-Dist: tomli>=1.1; python_version < "3.11"
23
24
  Provides-Extra: mcp
24
25
  Requires-Dist: mcp<2,>=1.0; extra == "mcp"
25
26
  Provides-Extra: browser
@@ -37,7 +38,7 @@ Dynamic: license-file
37
38
  </p>
38
39
 
39
40
  <p align="center">
40
- <img src="https://img.shields.io/badge/version-5.2.2-0D3B66?style=flat-square" alt="version">
41
+ <img src="https://img.shields.io/badge/version-5.2.4-0D3B66?style=flat-square" alt="version">
41
42
  <img src="https://img.shields.io/badge/python-3.10%2B-0D3B66?style=flat-square" alt="python">
42
43
  <img src="https://img.shields.io/badge/license-Dual-262626?style=flat-square" alt="license">
43
44
  <img src="https://img.shields.io/badge/stdlib%20only-262626?style=flat-square&label=dependencies" alt="dependencies">
@@ -193,30 +194,34 @@ BANTO SYNC VALIDATE — Testing 4 key(s)
193
194
 
194
195
  - macOS (uses Keychain for secret storage)
195
196
  - Python 3.10+
196
- - No external dependencies (MCP server: `pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"`)
197
+ - No external dependencies (MCP server: `pip install "banto[mcp]"`)
197
198
 
198
199
  ## ⚡ Quick start
199
200
 
200
201
  ### 1. Install
201
202
 
202
203
  ```bash
203
- # banto is not published on PyPI yet. Install directly from the GitHub repository.
204
-
205
204
  # uv (recommended)
206
- uv tool install "git+https://github.com/allnew-llc/banto.git"
205
+ uv tool install banto
207
206
 
208
207
  # pipx
209
- pipx install "git+https://github.com/allnew-llc/banto.git"
208
+ pipx install banto
210
209
 
211
210
  # pip
212
- pip install "git+https://github.com/allnew-llc/banto.git"
211
+ pip install banto
212
+
213
+ # With MCP server support
214
+ pip install "banto[mcp]"
213
215
 
214
216
  # From source
215
217
  git clone https://github.com/allnew-llc/banto.git
216
218
  cd banto
217
219
  pip install -e .
218
220
 
219
- # With MCP server support
221
+ # Latest development version from GitHub
222
+ uv tool install "git+https://github.com/allnew-llc/banto.git"
223
+ pipx install "git+https://github.com/allnew-llc/banto.git"
224
+ pip install "git+https://github.com/allnew-llc/banto.git"
220
225
  pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
221
226
  ```
222
227
 
@@ -258,7 +263,7 @@ banto budget --provider openai 30 # $30/month for OpenAI
258
263
  ## 💻 CLI reference
259
264
 
260
265
  <details>
261
- <summary><strong>Core commands (9)</strong></summary>
266
+ <summary><strong>Core commands (10)</strong></summary>
262
267
 
263
268
  | Command | Description |
264
269
  |---------|-------------|
@@ -271,6 +276,7 @@ banto budget --provider openai 30 # $30/month for OpenAI
271
276
  | `banto list` | List stored keys and budget summary |
272
277
  | `banto check <model> ...` | Dry-run budget check (`--tokens`, `--n`, `--seconds`, `--quality`, `--size`) |
273
278
  | `banto init` | Copy default config to `~/.config/banto/` |
279
+ | `banto version` | Print the installed version (`banto --version` / `banto -V` also work, including in broker mode) |
274
280
 
275
281
  </details>
276
282
 
@@ -358,7 +364,7 @@ Add to your `.mcp.json`:
358
364
  Requires the optional MCP dependency:
359
365
 
360
366
  ```bash
361
- pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
367
+ pip install "banto[mcp]"
362
368
  ```
363
369
 
364
370
  ### ChatGPT
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <img src="https://img.shields.io/badge/version-5.2.2-0D3B66?style=flat-square" alt="version">
12
+ <img src="https://img.shields.io/badge/version-5.2.4-0D3B66?style=flat-square" alt="version">
13
13
  <img src="https://img.shields.io/badge/python-3.10%2B-0D3B66?style=flat-square" alt="python">
14
14
  <img src="https://img.shields.io/badge/license-Dual-262626?style=flat-square" alt="license">
15
15
  <img src="https://img.shields.io/badge/stdlib%20only-262626?style=flat-square&label=dependencies" alt="dependencies">
@@ -165,30 +165,34 @@ BANTO SYNC VALIDATE — Testing 4 key(s)
165
165
 
166
166
  - macOS (uses Keychain for secret storage)
167
167
  - Python 3.10+
168
- - No external dependencies (MCP server: `pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"`)
168
+ - No external dependencies (MCP server: `pip install "banto[mcp]"`)
169
169
 
170
170
  ## ⚡ Quick start
171
171
 
172
172
  ### 1. Install
173
173
 
174
174
  ```bash
175
- # banto is not published on PyPI yet. Install directly from the GitHub repository.
176
-
177
175
  # uv (recommended)
178
- uv tool install "git+https://github.com/allnew-llc/banto.git"
176
+ uv tool install banto
179
177
 
180
178
  # pipx
181
- pipx install "git+https://github.com/allnew-llc/banto.git"
179
+ pipx install banto
182
180
 
183
181
  # pip
184
- pip install "git+https://github.com/allnew-llc/banto.git"
182
+ pip install banto
183
+
184
+ # With MCP server support
185
+ pip install "banto[mcp]"
185
186
 
186
187
  # From source
187
188
  git clone https://github.com/allnew-llc/banto.git
188
189
  cd banto
189
190
  pip install -e .
190
191
 
191
- # With MCP server support
192
+ # Latest development version from GitHub
193
+ uv tool install "git+https://github.com/allnew-llc/banto.git"
194
+ pipx install "git+https://github.com/allnew-llc/banto.git"
195
+ pip install "git+https://github.com/allnew-llc/banto.git"
192
196
  pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
193
197
  ```
194
198
 
@@ -230,7 +234,7 @@ banto budget --provider openai 30 # $30/month for OpenAI
230
234
  ## 💻 CLI reference
231
235
 
232
236
  <details>
233
- <summary><strong>Core commands (9)</strong></summary>
237
+ <summary><strong>Core commands (10)</strong></summary>
234
238
 
235
239
  | Command | Description |
236
240
  |---------|-------------|
@@ -243,6 +247,7 @@ banto budget --provider openai 30 # $30/month for OpenAI
243
247
  | `banto list` | List stored keys and budget summary |
244
248
  | `banto check <model> ...` | Dry-run budget check (`--tokens`, `--n`, `--seconds`, `--quality`, `--size`) |
245
249
  | `banto init` | Copy default config to `~/.config/banto/` |
250
+ | `banto version` | Print the installed version (`banto --version` / `banto -V` also work, including in broker mode) |
246
251
 
247
252
  </details>
248
253
 
@@ -330,7 +335,7 @@ Add to your `.mcp.json`:
330
335
  Requires the optional MCP dependency:
331
336
 
332
337
  ```bash
333
- pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
338
+ pip install "banto[mcp]"
334
339
  ```
335
340
 
336
341
  ### ChatGPT
@@ -30,4 +30,4 @@ __all__ = [
30
30
  "KeyNotFoundError",
31
31
  "ProfileManager",
32
32
  ]
33
- __version__ = "5.2.2"
33
+ __version__ = "5.2.4"
@@ -11,6 +11,7 @@ Commands:
11
11
  list List stored provider keys
12
12
  check <model> ... Dry-run budget check for a model
13
13
  init Copy default config to ~/.config/banto/
14
+ version Show the installed version (also --version, -V)
14
15
  """
15
16
 
16
17
  import getpass
@@ -475,6 +476,15 @@ def cmd_sealed(args: list[str]) -> None:
475
476
  cmd_sealed_dispatch(args)
476
477
 
477
478
 
479
+ def _print_version() -> None:
480
+ from . import __version__
481
+ print(f"banto {__version__}")
482
+
483
+
484
+ def cmd_version(args: list[str]) -> None:
485
+ _print_version()
486
+
487
+
478
488
  def cmd_chatgpt(args: list[str]) -> None:
479
489
  if not args or args[0] in ("-h", "--help"):
480
490
  print("banto chatgpt: Connect banto to ChatGPT\n")
@@ -503,10 +513,18 @@ COMMANDS = {
503
513
  "lease": cmd_lease,
504
514
  "sealed": cmd_sealed,
505
515
  "chatgpt": cmd_chatgpt,
516
+ "version": cmd_version,
506
517
  }
507
518
 
519
+ VERSION_FLAGS = ("--version", "-V", "version")
520
+
508
521
 
509
522
  def main() -> None:
523
+ # Handled before broker routing so it works in broker mode too and never
524
+ # touches Keychain, the broker socket, or config files.
525
+ if len(sys.argv) > 1 and sys.argv[1] in VERSION_FLAGS:
526
+ _print_version()
527
+ return
510
528
  if len(sys.argv) > 1 and sys.argv[1] == "broker":
511
529
  from .broker import main as broker_main
512
530
  sys.argv.pop(1)
@@ -518,7 +536,8 @@ def main() -> None:
518
536
  return
519
537
  if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
520
538
  print("banto: Budget-gated API key vault for LLM applications\n")
521
- print("Usage: banto <command> [args]\n")
539
+ print("Usage: banto <command> [args]")
540
+ print(" banto --version | -V\n")
522
541
  print("Commands:")
523
542
  print(" broker <cmd> Common Keychain process: install, health, enable, call")
524
543
  print(" status Show budget status (with per-provider/model breakdown)")
@@ -534,6 +553,7 @@ def main() -> None:
534
553
  print(" lease <cmd> Dynamic secrets with TTL and auto-revoke")
535
554
  print(" sealed <cmd> Secure Enclave sealed signing (non-extractable keys)")
536
555
  print(" chatgpt connect Connect banto to ChatGPT (starts tunnel)")
556
+ print(" version Show the installed version (also --version, -V)")
537
557
  print()
538
558
  print("Budget examples:")
539
559
  print(" banto budget Show all limits")
@@ -59,9 +59,20 @@ class BrokerClient:
59
59
  with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection:
60
60
  connection.settimeout(self.timeout)
61
61
  connection.connect(str(self.path))
62
- connection.sendall(encode_message({"operation": operation, "arguments": arguments}))
62
+ send_error = None
63
+ try:
64
+ connection.sendall(encode_message({"operation": operation, "arguments": arguments}))
65
+ except (BrokenPipeError, ConnectionResetError) as error:
66
+ # The broker may reply (e.g. peer_denied) and close before reading
67
+ # the request; its reply is still buffered, so read it.
68
+ send_error = error
63
69
  with connection.makefile("rb") as stream:
64
- result = read_message(stream)
70
+ try:
71
+ result = read_message(stream)
72
+ except BrokerError:
73
+ if send_error is not None:
74
+ raise send_error from None
75
+ raise
65
76
  except (OSError, ValueError):
66
77
  raise BrokerError("broker_unavailable_or_outcome_unknown; do not retry mutations blindly") from None
67
78
  if not result.get("ok"):
@@ -45,7 +45,10 @@ def json_update(text: str, entry: dict) -> str:
45
45
 
46
46
 
47
47
  def toml_update(text: str, command: str) -> str:
48
- import tomllib
48
+ try:
49
+ import tomllib
50
+ except ModuleNotFoundError: # Python 3.10: tomllib was added in 3.11
51
+ import tomli as tomllib
49
52
  parsed = tomllib.loads(text)
50
53
  entry = {"command": command, "args": [], "enabled": True}
51
54
  existing = parsed.get("mcp_servers", {}).get("banto")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: banto
3
- Version: 5.2.2
3
+ Version: 5.2.4
4
4
  Summary: Local-first secret management with optional budget gating and dynamic leases
5
5
  Author: AllNew LLC
6
6
  License-Expression: LicenseRef-Dual
@@ -20,6 +20,7 @@ Classifier: Topic :: Software Development :: Libraries
20
20
  Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
+ Requires-Dist: tomli>=1.1; python_version < "3.11"
23
24
  Provides-Extra: mcp
24
25
  Requires-Dist: mcp<2,>=1.0; extra == "mcp"
25
26
  Provides-Extra: browser
@@ -37,7 +38,7 @@ Dynamic: license-file
37
38
  </p>
38
39
 
39
40
  <p align="center">
40
- <img src="https://img.shields.io/badge/version-5.2.2-0D3B66?style=flat-square" alt="version">
41
+ <img src="https://img.shields.io/badge/version-5.2.4-0D3B66?style=flat-square" alt="version">
41
42
  <img src="https://img.shields.io/badge/python-3.10%2B-0D3B66?style=flat-square" alt="python">
42
43
  <img src="https://img.shields.io/badge/license-Dual-262626?style=flat-square" alt="license">
43
44
  <img src="https://img.shields.io/badge/stdlib%20only-262626?style=flat-square&label=dependencies" alt="dependencies">
@@ -193,30 +194,34 @@ BANTO SYNC VALIDATE — Testing 4 key(s)
193
194
 
194
195
  - macOS (uses Keychain for secret storage)
195
196
  - Python 3.10+
196
- - No external dependencies (MCP server: `pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"`)
197
+ - No external dependencies (MCP server: `pip install "banto[mcp]"`)
197
198
 
198
199
  ## ⚡ Quick start
199
200
 
200
201
  ### 1. Install
201
202
 
202
203
  ```bash
203
- # banto is not published on PyPI yet. Install directly from the GitHub repository.
204
-
205
204
  # uv (recommended)
206
- uv tool install "git+https://github.com/allnew-llc/banto.git"
205
+ uv tool install banto
207
206
 
208
207
  # pipx
209
- pipx install "git+https://github.com/allnew-llc/banto.git"
208
+ pipx install banto
210
209
 
211
210
  # pip
212
- pip install "git+https://github.com/allnew-llc/banto.git"
211
+ pip install banto
212
+
213
+ # With MCP server support
214
+ pip install "banto[mcp]"
213
215
 
214
216
  # From source
215
217
  git clone https://github.com/allnew-llc/banto.git
216
218
  cd banto
217
219
  pip install -e .
218
220
 
219
- # With MCP server support
221
+ # Latest development version from GitHub
222
+ uv tool install "git+https://github.com/allnew-llc/banto.git"
223
+ pipx install "git+https://github.com/allnew-llc/banto.git"
224
+ pip install "git+https://github.com/allnew-llc/banto.git"
220
225
  pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
221
226
  ```
222
227
 
@@ -258,7 +263,7 @@ banto budget --provider openai 30 # $30/month for OpenAI
258
263
  ## 💻 CLI reference
259
264
 
260
265
  <details>
261
- <summary><strong>Core commands (9)</strong></summary>
266
+ <summary><strong>Core commands (10)</strong></summary>
262
267
 
263
268
  | Command | Description |
264
269
  |---------|-------------|
@@ -271,6 +276,7 @@ banto budget --provider openai 30 # $30/month for OpenAI
271
276
  | `banto list` | List stored keys and budget summary |
272
277
  | `banto check <model> ...` | Dry-run budget check (`--tokens`, `--n`, `--seconds`, `--quality`, `--size`) |
273
278
  | `banto init` | Copy default config to `~/.config/banto/` |
279
+ | `banto version` | Print the installed version (`banto --version` / `banto -V` also work, including in broker mode) |
274
280
 
275
281
  </details>
276
282
 
@@ -358,7 +364,7 @@ Add to your `.mcp.json`:
358
364
  Requires the optional MCP dependency:
359
365
 
360
366
  ```bash
361
- pip install "banto[mcp] @ git+https://github.com/allnew-llc/banto.git"
367
+ pip install "banto[mcp]"
362
368
  ```
363
369
 
364
370
  ### ChatGPT
@@ -101,6 +101,7 @@ banto/sync/notifiers/slack.py
101
101
  banto/sync/notifiers/teams.py
102
102
  tests/test_broker.py
103
103
  tests/test_broker_setup.py
104
+ tests/test_cli_version.py
104
105
  tests/test_ecdsa_p256.py
105
106
  tests/test_hold_timeout.py
106
107
  tests/test_host_validation.py
@@ -1,4 +1,7 @@
1
1
 
2
+ [:python_version < "3.11"]
3
+ tomli>=1.1
4
+
2
5
  [browser]
3
6
  playwright<2,>=1.45
4
7
 
@@ -4,12 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "banto"
7
- version = "5.2.2"
7
+ version = "5.2.4"
8
8
  description = "Local-first secret management with optional budget gating and dynamic leases"
9
9
  readme = "README.md"
10
10
  license = "LicenseRef-Dual"
11
11
  requires-python = ">=3.10"
12
12
  authors = [{ name = "AllNew LLC" }]
13
+ dependencies = ["tomli>=1.1; python_version < '3.11'"]
13
14
  keywords = ["llm", "api", "budget", "keychain", "macos", "security", "secrets", "sync", "vault", "lease", "dynamic-secrets"]
14
15
  classifiers = [
15
16
  "Development Status :: 4 - Beta",
@@ -51,6 +51,40 @@ def test_other_uid_denied(local_server, monkeypatch):
51
51
  client.call("health")
52
52
 
53
53
 
54
+ def test_reply_read_when_broker_closes_before_request(monkeypatch):
55
+ import socket
56
+ import tempfile
57
+ with tempfile.TemporaryDirectory(prefix="banto-", dir="/tmp") as directory:
58
+ path = Path(directory) / "b.sock"
59
+ listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
60
+ listener.bind(str(path))
61
+ os.chmod(path, 0o600)
62
+ listener.listen(1)
63
+ closed = threading.Event()
64
+ encode = broker_client.encode_message
65
+
66
+ def deny_and_close():
67
+ connection, _ = listener.accept()
68
+ connection.sendall(encode({"ok": False, "error": "peer_denied"}))
69
+ connection.close()
70
+ closed.set()
71
+
72
+ thread = threading.Thread(target=deny_and_close, daemon=True)
73
+ thread.start()
74
+
75
+ def encode_after_close(value):
76
+ assert closed.wait(5)
77
+ return encode(value)
78
+
79
+ monkeypatch.setattr(broker_client, "encode_message", encode_after_close)
80
+ try:
81
+ with pytest.raises(BrokerError, match="peer_denied"):
82
+ BrokerClient(path).call("health")
83
+ finally:
84
+ thread.join(5)
85
+ listener.close()
86
+
87
+
54
88
  def test_exception_does_not_leak(local_server):
55
89
  server, client = local_server
56
90
  def fail(*args):
@@ -0,0 +1,58 @@
1
+ # Copyright 2025-2026 AllNew LLC
2
+ # Licensed under LicenseRef-Dual (see LICENSE)
3
+ """Tests for `banto --version` / `-V` / `version`."""
4
+
5
+ import subprocess
6
+ import sys
7
+ from unittest.mock import Mock
8
+
9
+ import pytest
10
+
11
+ import banto
12
+ from banto import __main__ as cli
13
+ from banto import broker_cli
14
+ from banto.broker_client import BrokerClient
15
+
16
+ FLAGS = ["--version", "-V", "version"]
17
+
18
+
19
+ @pytest.mark.parametrize("flag", FLAGS)
20
+ def test_version_prints_and_exits_zero(flag, monkeypatch, capsys):
21
+ monkeypatch.setattr(sys, "argv", ["banto", flag])
22
+ cli.main() # returns normally (exit status 0)
23
+ assert capsys.readouterr().out == f"banto {banto.__version__}\n"
24
+
25
+
26
+ @pytest.mark.parametrize("flag", FLAGS)
27
+ def test_version_works_in_broker_mode(flag, tmp_path, monkeypatch, capsys):
28
+ (tmp_path / "required").touch()
29
+ monkeypatch.setattr(broker_cli, "runtime_dir", lambda: tmp_path)
30
+ call = Mock(side_effect=AssertionError("broker must not be called"))
31
+ monkeypatch.setattr(BrokerClient, "call", call)
32
+ monkeypatch.setattr(sys, "argv", ["banto", flag])
33
+ cli.main()
34
+ assert capsys.readouterr().out == f"banto {banto.__version__}\n"
35
+ call.assert_not_called()
36
+
37
+
38
+ @pytest.mark.parametrize("flag", ["--version", "-V"])
39
+ def test_version_subprocess_exit_code(flag):
40
+ proc = subprocess.run(
41
+ [sys.executable, "-m", "banto", flag], capture_output=True, text=True, timeout=60
42
+ )
43
+ assert proc.returncode == 0
44
+ assert proc.stdout.strip() == f"banto {banto.__version__}"
45
+
46
+
47
+ def test_help_lists_version(monkeypatch, capsys):
48
+ monkeypatch.setattr(sys, "argv", ["banto", "--help"])
49
+ with pytest.raises(SystemExit) as exc:
50
+ cli.main()
51
+ assert exc.value.code == 0
52
+ out = capsys.readouterr().out
53
+ assert "--version" in out
54
+ assert "version " in out
55
+
56
+
57
+ def test_version_is_registered_command():
58
+ assert "version" in cli.COMMANDS
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