brindle 0.4.4__tar.gz → 0.4.6__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 (194) hide show
  1. {brindle-0.4.4 → brindle-0.4.6}/.gitignore +2 -1
  2. {brindle-0.4.4 → brindle-0.4.6}/PKG-INFO +185 -3
  3. {brindle-0.4.4 → brindle-0.4.6}/README.md +183 -2
  4. {brindle-0.4.4 → brindle-0.4.6}/pyproject.toml +2 -1
  5. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/agents.py +741 -137
  6. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/antigravity.py +54 -8
  7. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/autopilot.py +184 -46
  8. brindle-0.4.6/src/brindle/bus.py +75 -0
  9. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cli.py +222 -359
  10. brindle-0.4.6/src/brindle/clone.py +83 -0
  11. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cloud_login.py +220 -12
  12. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/codex_hook.py +73 -15
  13. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/company_identity.py +21 -15
  14. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/company_login.py +21 -7
  15. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/config.py +120 -7
  16. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/coordinator.py +11 -5
  17. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cull.py +30 -13
  18. brindle-0.4.6/src/brindle/daemon/__init__.py +6 -0
  19. brindle-0.4.6/src/brindle/daemon/api-schema.json +5912 -0
  20. brindle-0.4.6/src/brindle/daemon/app.py +52 -0
  21. brindle-0.4.6/src/brindle/daemon/auth.py +146 -0
  22. brindle-0.4.6/src/brindle/daemon/events.py +203 -0
  23. brindle-0.4.6/src/brindle/daemon/grants.py +320 -0
  24. brindle-0.4.6/src/brindle/daemon/holds.py +318 -0
  25. brindle-0.4.6/src/brindle/daemon/lifecycle.py +310 -0
  26. brindle-0.4.6/src/brindle/daemon/peer.py +116 -0
  27. brindle-0.4.6/src/brindle/daemon/routes_actions.py +1788 -0
  28. brindle-0.4.6/src/brindle/daemon/routes_org_permissions.py +27 -0
  29. brindle-0.4.6/src/brindle/daemon/routes_permissions.py +37 -0
  30. brindle-0.4.6/src/brindle/daemon/routes_read.py +584 -0
  31. brindle-0.4.6/src/brindle/daemon/schema.py +58 -0
  32. brindle-0.4.6/src/brindle/daemon/session_review.py +470 -0
  33. brindle-0.4.6/src/brindle/daemon/terminal.py +135 -0
  34. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/db.py +239 -38
  35. brindle-0.4.6/src/brindle/debuglog.py +410 -0
  36. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/doctor.py +97 -10
  37. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/events.py +16 -0
  38. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/gates.py +4 -0
  39. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/git.py +27 -9
  40. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/key_check.py +2 -1
  41. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/keys_cmds.py +30 -12
  42. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/knowledge.py +13 -0
  43. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/launch_health.py +25 -7
  44. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/login_pause.py +64 -12
  45. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/mcp_server.py +69 -85
  46. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/loop.py +5 -1
  47. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/permissions.py +8 -3
  48. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/runner.py +50 -1
  49. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/opencode.py +8 -3
  50. brindle-0.4.6/src/brindle/org_permissions.py +145 -0
  51. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pane_auth.py +7 -2
  52. brindle-0.4.6/src/brindle/permission_editor.py +350 -0
  53. brindle-0.4.6/src/brindle/permission_grants.py +646 -0
  54. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/permissions.py +606 -48
  55. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/picker.py +17 -4
  56. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pipeline.py +93 -23
  57. brindle-0.4.6/src/brindle/preflight.py +291 -0
  58. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/account.py +25 -14
  59. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/team_policy.py +145 -29
  60. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/profiles.py +58 -1
  61. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/prompt_answer.py +82 -30
  62. brindle-0.4.6/src/brindle/provider_homes.py +287 -0
  63. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/providers.py +215 -27
  64. brindle-0.4.6/src/brindle/providers_install.py +268 -0
  65. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/quota.py +3 -1
  66. brindle-0.4.6/src/brindle/service/__init__.py +4 -0
  67. brindle-0.4.6/src/brindle/service/dto.py +537 -0
  68. brindle-0.4.6/src/brindle/service/errors.py +60 -0
  69. brindle-0.4.6/src/brindle/service/inbox.py +502 -0
  70. brindle-0.4.6/src/brindle/service/routing.py +119 -0
  71. brindle-0.4.6/src/brindle/service/sessions.py +509 -0
  72. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/sessions.py +47 -7
  73. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/sidebar_follow.py +81 -8
  74. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/status_cache.py +50 -0
  75. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/tasks.py +252 -63
  76. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/tmux.py +140 -12
  77. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/upgrade.py +36 -8
  78. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/view.py +65 -3
  79. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/watch.py +107 -71
  80. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/workspaces.py +52 -15
  81. {brindle-0.4.4 → brindle-0.4.6}/LICENSE +0 -0
  82. {brindle-0.4.4 → brindle-0.4.6}/SCHEDULE-A +0 -0
  83. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/__init__.py +0 -0
  84. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/__main__.py +0 -0
  85. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/account.py +0 -0
  86. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/airgap.py +0 -0
  87. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/assembler.py +0 -0
  88. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/assets/braid_dark.png +0 -0
  89. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/assets/braid_light.png +0 -0
  90. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/budget.py +0 -0
  91. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/__init__.py +0 -0
  92. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/backend-auditor.md +0 -0
  93. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/coordinator.md +0 -0
  94. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-codex.md +0 -0
  95. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-fable.md +0 -0
  96. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-gemini.md +0 -0
  97. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-heavy.md +0 -0
  98. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-light.md +0 -0
  99. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-local.md +0 -0
  100. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-opencode.md +0 -0
  101. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-sonnet.md +0 -0
  102. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer.md +0 -0
  103. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/qa.md +0 -0
  104. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
  105. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-gemini.md +0 -0
  106. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-local.md +0 -0
  107. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-opencode.md +0 -0
  108. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer.md +0 -0
  109. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/subagent.md +0 -0
  110. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/supervisor.md +0 -0
  111. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/__init__.py +0 -0
  112. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/security/backend.md +0 -0
  113. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
  114. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/tests/only.md +0 -0
  115. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_adapters.py +0 -0
  116. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_client.py +0 -0
  117. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_federation.py +0 -0
  118. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_hosts.py +0 -0
  119. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cloud_credential.py +0 -0
  120. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/codemap.py +0 -0
  121. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/codemap_docs.py +0 -0
  122. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/conflicts.py +0 -0
  123. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cost.py +0 -0
  124. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cost_estimate.py +0 -0
  125. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/demo.py +0 -0
  126. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/detect.py +0 -0
  127. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/escapes.py +0 -0
  128. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/guardrails.py +0 -0
  129. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/history.py +0 -0
  130. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/identity_lock.py +0 -0
  131. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/inbox.py +0 -0
  132. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/kb.py +0 -0
  133. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/keystore.py +0 -0
  134. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/learned_rules.py +0 -0
  135. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/learning.py +0 -0
  136. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/legacy.py +0 -0
  137. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/local_setup.py +0 -0
  138. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/model_access.py +0 -0
  139. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/model_availability.py +0 -0
  140. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/__init__.py +0 -0
  141. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/catalog.py +0 -0
  142. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/client.py +0 -0
  143. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/lsp.py +0 -0
  144. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/serve.py +0 -0
  145. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/tools.py +0 -0
  146. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/plugins.py +0 -0
  147. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/policy.py +0 -0
  148. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pool.py +0 -0
  149. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pricing.py +0 -0
  150. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/__init__.py +0 -0
  151. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/_files.py +0 -0
  152. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/audit_chain.py +0 -0
  153. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/audit_export.py +0 -0
  154. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/auth.py +0 -0
  155. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/brief_learning.py +0 -0
  156. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/cost_centers.py +0 -0
  157. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/credentials.py +0 -0
  158. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/features.py +0 -0
  159. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/keys.py +0 -0
  160. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/learning.py +0 -0
  161. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/license.py +0 -0
  162. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/loopback.py +0 -0
  163. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/managed_models.py +0 -0
  164. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/org_profiles.py +0 -0
  165. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/orgkey.py +0 -0
  166. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/rollout.py +0 -0
  167. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/route_health.py +0 -0
  168. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/settings_sync.py +0 -0
  169. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/status.py +0 -0
  170. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/team_events.py +0 -0
  171. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/procs.py +0 -0
  172. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/repo_cmds.py +0 -0
  173. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/repos.py +0 -0
  174. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/rewind.py +0 -0
  175. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/rule_checks.py +0 -0
  176. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/savings.py +0 -0
  177. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/scratch.py +0 -0
  178. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/secrets.py +0 -0
  179. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/self_serve.py +0 -0
  180. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/services.py +0 -0
  181. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/sidebar_logo.py +0 -0
  182. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/signin_pause.py +0 -0
  183. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/updates.py +0 -0
  184. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/usage.py +0 -0
  185. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/wake.py +0 -0
  186. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/watch_costs.py +0 -0
  187. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/__init__.py +0 -0
  188. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/chat.py +0 -0
  189. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/dashboard.py +0 -0
  190. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/diff.py +0 -0
  191. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/server.py +0 -0
  192. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/static/app.js +0 -0
  193. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/static/index.html +0 -0
  194. {brindle-0.4.4 → brindle-0.4.6}/src/brindle/worker_models.py +0 -0
@@ -9,7 +9,8 @@ graphify-out/
9
9
  .brindle/*.log
10
10
  .brindle/agents/
11
11
  # private, kept local for now
12
- docs/
12
+ docs/*
13
+ !docs/agents-and-routing.md
13
14
  .brindle/config.local.json
14
15
  .brindle/bench.json
15
16
  .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brindle
3
- Version: 0.4.4
3
+ Version: 0.4.6
4
4
  Summary: Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges
5
5
  Project-URL: Homepage, https://pawdelta.com/brindle/
6
6
  Project-URL: Documentation, https://pawdelta.com/brindle/docs/recommended-use
@@ -27,6 +27,7 @@ Requires-Dist: cryptography>=42
27
27
  Requires-Dist: mcp<3,>=2.2
28
28
  Requires-Dist: packaging>=24
29
29
  Requires-Dist: typer>=0.12
30
+ Requires-Dist: wsproto>=1.2
30
31
  Description-Content-Type: text/markdown
31
32
 
32
33
  # brindle
@@ -100,6 +101,8 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
100
101
 
101
102
  brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
102
103
 
104
+ There's also a free brindle desktop app for macOS, Linux, and Windows (via WSL). It's a download from the brindle website and runs on the same engine; the CLI keeps working alongside it. `brindle app` opens the app, or prints the download link if it isn't installed. `brindle daemon` runs the local API the app talks to: over a Unix socket by default, or on `--tcp` at 127.0.0.1 for WSL. Over `--tcp` the daemon can't check whether a caller is an agent, so its token is the only gate: keep TCP for the WSL client.
105
+
103
106
  `brindle --version` prints the installed version; the tmux status bar of every brindle
104
107
  session shows it too (`brindle 0.4.3`). A session started before an upgrade keeps
105
108
  running the old code, and shows the old number, until you restart it.
@@ -667,6 +670,7 @@ Autopilot, merge gates and cleanup:
667
670
  | Key | Default | |
668
671
  |---|---|---|
669
672
  | `autopilot` | `true` | start the supervisor with autopilot on |
673
+ | `claude_permission_mode` | `"default"` | the `--permission-mode` for Claude Code sessions whose profile sets no `permission_mode`; the default holds permission requests for the brindle app, set `"auto"` to opt into auto mode. Only read from `~/.brindle/config.json` |
670
674
  | `auto_update` | `false` | run `uv tool upgrade brindle` in the background at session start when PyPI has a newer version; restart to use it. Can also be set in `~/.brindle/config.json` for every repo |
671
675
  | `checks` | `[]` | commands that must pass in a worker's branch before it merges |
672
676
  | `review` | only under autopilot | require a reviewer's approval before merging |
@@ -730,6 +734,9 @@ When a workspace is created (including from the pool), brindle starts one contai
730
734
 
731
735
  ### Routing by weight
732
736
 
737
+ For a walkthrough of agents, routing, rules and org limits, see
738
+ [Agents and routing](docs/agents-and-routing.md).
739
+
733
740
  The supervisor sizes a task and passes `weight` (`"light"`, `"medium"`,
734
741
  `"heavy"` or `"advanced"`, with `"reasoning"` and `"complex"` accepted as aliases) to
735
742
  `assign`/`handoff`; brindle picks an available profile for that tier.
@@ -912,6 +919,64 @@ on without the tool running counts for nothing. Every decision is in
912
919
  `brindle history --kind permission`, and when a worker sits on a prompt, its
913
920
  supervisor is told exactly which request is pending (only you can answer it).
914
921
 
922
+ **Grants.** When the app holds a Claude Code or Codex worker's request in the
923
+ Inbox, you can answer **Allow for N min**. That lets this agent repeat exactly
924
+ this request (the whole command, the resolved path, the https URL or the tool)
925
+ in this repo until the time or the uses run out, or until you revoke it under
926
+ Settings → Permission grants. A grant is spent when the hook allows the call,
927
+ not when the tool succeeds (a hook can't see the result). Grants live in the
928
+ daemon's memory, so a restart ends them all.
929
+
930
+ Two built-in denies can also be lifted by an explicit approval. When nothing else
931
+ denies the request, it is still denied at once, never held. A held request
932
+ shows the CLI's own prompt, and an answer typed there would run it. The worker
933
+ is told it was refused and that it asked you, and the Inbox shows the exact
934
+ request with **Allow once**, **Deny**, and, when policy permits, **Always allow in this repository**. Allow it once, and the worker's next
935
+ try of exactly that command uses your one-time grant. What it covers and what
936
+ the Inbox shows are worked out by the daemon from the agent's own workspace,
937
+ never taken from the agent. The grant expires after 15 minutes, or sooner if
938
+ your org's `max_minutes` is shorter, even when the org shortens it later. It is
939
+ used up even if the push then fails. If nobody answers, or the
940
+ daemon isn't running, it stays denied.
941
+
942
+ **Remembered approvals** are saved in your user permission preferences, across
943
+ sessions and daemon restarts, until you revoke them in Settings → Permission
944
+ grants. A remembered push covers the same repository, remote URL and destination
945
+ branch across future commits; a file approval covers that specific file.
946
+ Every use still checks the current policy, explicit denies and adapter protections.
947
+ Temporary time/use limits apply to temporary grants; persistent approvals require
948
+ their own policy permission. Turning that permission off prevents their use.
949
+ - `git push` qualifies only as `git push <remote> <full commit sha>:refs/heads/<branch>`, run in the agent's own worktree (not another clone or a nested repository), and only when:
950
+ - the remote resolves (insteadOf applied) to exactly one https, ssh or `user@host:path` URL;
951
+ - none of `core.sshCommand`, `core.hooksPath`, the remote's receivepack/vcs/mirror, `push.followTags` or `push.recurseSubmodules` is set;
952
+ - there is no pre-push or reference-transaction hook;
953
+ - neither the repo nor your org sets protected paths (a push skips the merge check that enforces them).
954
+ - `.env*` qualifies for one file inside the worktree.
955
+
956
+ After your answer, brindle checks the policy, every rule and that scope again
957
+ before allowing. Anything that changed meanwhile refuses it. The command then
958
+ runs outside brindle.
959
+
960
+ Never lifted: your own, the repo's, a profile's or your org's denies, git force
961
+ flags, and reads of `~/.ssh`, `~/.aws`, `~/.gnupg` or the daemon's token.
962
+ Antigravity and OpenCode workers get no grants: agy ignores a hook's allow, and
963
+ OpenCode's plugin has no hold.
964
+
965
+ In a Team org, only the org policy's `permission_grants` block decides what may
966
+ be granted. It is read again on every request:
967
+
968
+ ```json
969
+ "permission_grants": {"allow": true, "kinds": ["bash", "fetch"], "overridable": ["git-push"],
970
+ "max_minutes": 30, "max_uses": 5, "allow_persistent": true,
971
+ "deny": ["bash prefix terraform apply"]}
972
+ ```
973
+
974
+ - `deny` entries are enforced for every provider and can't be lifted.
975
+ - `allow_persistent` permits remembered overrides of these two built-in denies; omitted or false allows only temporary overrides. This setting does not control ordinary saved allow rules. The admin policy editor is not delivered by this local engine change.
976
+ - A policy without the block, an unknown or malformed field (which makes the whole policy unusable), a policy that can't be read, or a paused or shut-down member: no grants.
977
+ - Without a Team org, any kind can be granted, for up to 60 min and 20 uses. Both built-in denies support one-time or remembered approvals.
978
+ - The org backend that serves this block isn't part of this repository. Until it serves one, Team orgs get no grants. It is enforced from the cached policy, or in air-gap mode from `.brindle/policy.json`.
979
+
915
980
  **Codex.** Codex runs a hook only once it's trusted, so with
916
981
  `permission_policy` on, brindle trusts its own hook the first time a Codex worker
917
982
  needs it (and says so at `brindle start`). It adds one entry,
@@ -2104,8 +2169,10 @@ folder; `brindle profile new` writes one.
2104
2169
  ---
2105
2170
  name: developer-bedrock
2106
2171
  provider: claude
2172
+ auth: cloud
2107
2173
  model: us.anthropic.claude-haiku-4-5-20251001-v1:0 # a Bedrock model id your account can use
2108
2174
  env.CLAUDE_CODE_USE_BEDROCK: 1
2175
+ env.AWS_PROFILE: company
2109
2176
  env.AWS_REGION: us-east-1
2110
2177
  ---
2111
2178
  ```
@@ -2132,10 +2199,125 @@ list it under `routing` in `.brindle/config.json`.
2132
2199
  `"modelProvider": "gemini"` in `~/.gemini/antigravity-cli/settings.json`, and
2133
2200
  export `GEMINI_API_KEY` in your shell, or put it in a profile in your user
2134
2201
  profiles folder; never in a profile under `.brindle/agents/`, which is committed. For a
2135
- Gemini-compatible gateway, set `env.GOOGLE_GEMINI_BASE_URL` to its URL. As of
2136
- 1.3.4, `agy` ignores Vertex AI credentials, so use a Gemini API key or gateway.
2202
+ Gemini-compatible gateway, set `env.GOOGLE_GEMINI_BASE_URL` to its URL.
2203
+ The Vertex login path in `agy` has not been validated by brindle; see the
2204
+ OpenCode alternative below for Gemini on Vertex.
2137
2205
  The full setup is in [Google Antigravity](#google-antigravity) below.
2138
2206
 
2207
+ For Claude on an existing AWS SSO profile or Google ADC, launch preflight checks
2208
+ the configured cloud login and offers its cloud sign-in instead of Claude's
2209
+ personal sign-in. The action keeps the profile's project, region and credential
2210
+ location through both desktop sign-in and the completion check; repeated presses
2211
+ reuse its running terminal. Custom credential files offer a manual refresh check.
2212
+ Desktop and automatic sign-in share the credential source's lock, so a second
2213
+ flow leaves an existing cloud login running.
2214
+ `brindle doctor` groups
2215
+ profiles by cloud identity and reports credential availability, expiry when
2216
+ known, and refresh instructions. AWS account and role are selected by the named
2217
+ SSO profile. To isolate Google ADC, set `env.CLOUDSDK_CONFIG` to that identity's
2218
+ gcloud directory alongside the Vertex project and region.
2219
+ Use `auth: cloud` to remove inherited model API keys, including Bedrock bearer
2220
+ keys that would otherwise bypass the AWS SSO profile.
2221
+
2222
+ `GOOGLE_APPLICATION_CREDENTIALS` selects a specific credential file. A browser
2223
+ ADC login writes the selected gcloud directory's default file. If the explicit
2224
+ credential file is elsewhere, brindle asks you to refresh it directly instead of
2225
+ opening a login that would update a different identity. Existing
2226
+ federated credentials and non-SSO AWS credential chains keep their existing
2227
+ handling; the new profile checks do not validate their expiry. Expired cloud
2228
+ credentials hold session delivery until refreshed, including when a cloud
2229
+ worker belongs to a Codex or other provider's supervisor. A session using several
2230
+ cloud identities stays held until all of them work again.
2231
+
2232
+ Company-cloud routes (#265):
2233
+
2234
+ | Model route | Agent and identity | Validation still needed |
2235
+ | --- | --- | --- |
2236
+ | Claude on Bedrock | Claude Code, AWS SSO profile and `AWS_REGION` | Live cloud validation |
2237
+ | Claude on Vertex | Claude Code, ADC, `ANTHROPIC_VERTEX_PROJECT_ID` and `CLOUD_ML_REGION` | Live cloud validation |
2238
+ | Gemini on Vertex | OpenCode, `auth: cloud`, `google-vertex/<model-id>`, project/region and ADC | Live cloud validation |
2239
+ | Azure OpenAI | OpenCode, `auth: cloud`, `azure/<deployment>`, Azure CLI and Entra connection | Live cloud validation |
2240
+
2241
+ For Gemini Vertex, put these fields in an agent profile (choose a model available
2242
+ in your project and region):
2243
+
2244
+ ```yaml
2245
+ provider: opencode
2246
+ model: google-vertex/gemini-2.5-pro
2247
+ auth: cloud
2248
+ env.GOOGLE_CLOUD_PROJECT: your-project
2249
+ env.VERTEX_LOCATION: us-central1
2250
+ env.CLOUDSDK_CONFIG: /absolute/path/to/company-gcloud
2251
+ ```
2252
+
2253
+ brindle passes that directory's `application_default_credentials.json` explicitly
2254
+ to OpenCode's SDK and checks the same file, while retaining browser refresh through
2255
+ gcloud. The profile's project and region override inherited OpenCode aliases.
2256
+ An explicit profile `CLOUDSDK_CONFIG` also overrides an inherited ADC file selector;
2257
+ set `env.GOOGLE_APPLICATION_CREDENTIALS` in the profile to select a custom file instead.
2258
+
2259
+ For Azure OpenAI, select a separate Azure CLI directory for this identity and
2260
+ your deployed model's name:
2261
+
2262
+ ```yaml
2263
+ provider: opencode
2264
+ model: azure/your-deployment
2265
+ auth: cloud
2266
+ env.AZURE_CONFIG_DIR: /absolute/path/to/company-azure
2267
+ env.AZURE_RESOURCE_NAME: your-resource
2268
+ env.AZURE_TENANT_ID: your-tenant
2269
+ env.AZURE_SUBSCRIPTION_ID: your-subscription
2270
+ ```
2271
+
2272
+ Azure's directory and resource are required. Tenant and subscription, when set,
2273
+ must match the CLI's active identity. The sign-in action runs `az login` for that
2274
+ identity when needed, then OpenCode's `auth login --provider azure --method
2275
+ "Microsoft Entra ID (Azure CLI)"`. It checks a Cognitive Services access token
2276
+ and the connection for the selected resource before allowing launch. It never
2277
+ switches the default Azure CLI configuration behind your back.
2278
+
2279
+ OpenCode cloud profiles receive an isolated connection store beneath
2280
+ `~/.brindle/cloud-opencode/` (or `BRINDLE_HOME`), keyed by their cloud identity
2281
+ and project/resource. `auth: cloud` removes inherited API keys that could select
2282
+ personal billing. Existing API-key profiles keep their behavior: a qualified
2283
+ model alone does not enable cloud login. Both cloud paths share profile-aware
2284
+ preflight/doctor checks, one in-flight sign-in per credential source, expiry
2285
+ holds, and live/paused-session recovery.
2286
+
2287
+ OpenCode's [provider documentation](https://opencode.ai/docs/providers/) describes
2288
+ both alternatives. For Azure, its Entra connection is a required setup step;
2289
+ an Azure endpoint alone does not select CLI authentication. This is Azure OpenAI,
2290
+ separate from Claude on Foundry. The installed CLI inspected for these options
2291
+ was OpenCode 1.19.3. brindle's native client can send bearer-authenticated OpenAI
2292
+ chat requests, but it does not yet acquire or renew Google/Azure CLI tokens.
2293
+ Those routes need token lifecycle support before native cloud login can be claimed.
2294
+
2295
+ Focused fake-based checks:
2296
+
2297
+ ```sh
2298
+ uv run pytest tests/test_daemon_actions.py -q -k 'cloud_profile_login or opencode_cloud_identity or login_and_secret or a_sign_in or preflight'
2299
+ uv run pytest tests/test_cloud_login.py -q
2300
+ uv run pytest tests/test_login_pause.py -q
2301
+ uv run pytest tests/test_doctor_signin.py -q
2302
+ uv run pytest tests/test_auth_failure_handling.py -q
2303
+ uv run pytest tests/test_opencode_provider.py -q
2304
+ uv run pytest tests/test_profile_auth.py -q
2305
+ npm --prefix app test -- --run src/screens/New/NewSession.test.tsx
2306
+ npm --prefix app test -- --run src/components/ProviderSignIn.test.tsx
2307
+ npm --prefix app run typecheck
2308
+ ```
2309
+
2310
+ `cloud_profile_login` and `opencode_cloud_identity` exercise real profile/environment
2311
+ resolution and HTTP login/preflight dispatch, identity propagation, duplicate
2312
+ actions, doctor expiry and live/paused credential-block recovery. The login-pause
2313
+ tests cover structured OpenCode expiry events, cross-provider supervisors,
2314
+ multiple expired identities and held-message delivery after recovery.
2315
+ Cloud commands and panes are fakes. No live AWS SSO, Google ADC, Azure login,
2316
+ model inference, or user cloud-config changes were used for validation. Provider
2317
+ permissions, deployed model availability, browser flows and token renewal against
2318
+ real cloud services still need manual validation. Gemini/Azure use OpenCode;
2319
+ native SDK and Antigravity cloud integration are not added by this change.
2320
+
2139
2321
  If your org runs brindle for a team, its admins can set a route for everyone in
2140
2322
  the org's Routing settings on the brindle dashboard; an enforced org route replaces
2141
2323
  a profile's `env.` lines, so a profile can't move a worker off the company's route. See
@@ -69,6 +69,8 @@ uv tool install --editable ~/Projects/brindle # or from a local checkout
69
69
 
70
70
  brindle drives Claude Code, so you need that too (`npm install -g @anthropic-ai/claude-code`).
71
71
 
72
+ There's also a free brindle desktop app for macOS, Linux, and Windows (via WSL). It's a download from the brindle website and runs on the same engine; the CLI keeps working alongside it. `brindle app` opens the app, or prints the download link if it isn't installed. `brindle daemon` runs the local API the app talks to: over a Unix socket by default, or on `--tcp` at 127.0.0.1 for WSL. Over `--tcp` the daemon can't check whether a caller is an agent, so its token is the only gate: keep TCP for the WSL client.
73
+
72
74
  `brindle --version` prints the installed version; the tmux status bar of every brindle
73
75
  session shows it too (`brindle 0.4.3`). A session started before an upgrade keeps
74
76
  running the old code, and shows the old number, until you restart it.
@@ -636,6 +638,7 @@ Autopilot, merge gates and cleanup:
636
638
  | Key | Default | |
637
639
  |---|---|---|
638
640
  | `autopilot` | `true` | start the supervisor with autopilot on |
641
+ | `claude_permission_mode` | `"default"` | the `--permission-mode` for Claude Code sessions whose profile sets no `permission_mode`; the default holds permission requests for the brindle app, set `"auto"` to opt into auto mode. Only read from `~/.brindle/config.json` |
639
642
  | `auto_update` | `false` | run `uv tool upgrade brindle` in the background at session start when PyPI has a newer version; restart to use it. Can also be set in `~/.brindle/config.json` for every repo |
640
643
  | `checks` | `[]` | commands that must pass in a worker's branch before it merges |
641
644
  | `review` | only under autopilot | require a reviewer's approval before merging |
@@ -699,6 +702,9 @@ When a workspace is created (including from the pool), brindle starts one contai
699
702
 
700
703
  ### Routing by weight
701
704
 
705
+ For a walkthrough of agents, routing, rules and org limits, see
706
+ [Agents and routing](docs/agents-and-routing.md).
707
+
702
708
  The supervisor sizes a task and passes `weight` (`"light"`, `"medium"`,
703
709
  `"heavy"` or `"advanced"`, with `"reasoning"` and `"complex"` accepted as aliases) to
704
710
  `assign`/`handoff`; brindle picks an available profile for that tier.
@@ -881,6 +887,64 @@ on without the tool running counts for nothing. Every decision is in
881
887
  `brindle history --kind permission`, and when a worker sits on a prompt, its
882
888
  supervisor is told exactly which request is pending (only you can answer it).
883
889
 
890
+ **Grants.** When the app holds a Claude Code or Codex worker's request in the
891
+ Inbox, you can answer **Allow for N min**. That lets this agent repeat exactly
892
+ this request (the whole command, the resolved path, the https URL or the tool)
893
+ in this repo until the time or the uses run out, or until you revoke it under
894
+ Settings → Permission grants. A grant is spent when the hook allows the call,
895
+ not when the tool succeeds (a hook can't see the result). Grants live in the
896
+ daemon's memory, so a restart ends them all.
897
+
898
+ Two built-in denies can also be lifted by an explicit approval. When nothing else
899
+ denies the request, it is still denied at once, never held. A held request
900
+ shows the CLI's own prompt, and an answer typed there would run it. The worker
901
+ is told it was refused and that it asked you, and the Inbox shows the exact
902
+ request with **Allow once**, **Deny**, and, when policy permits, **Always allow in this repository**. Allow it once, and the worker's next
903
+ try of exactly that command uses your one-time grant. What it covers and what
904
+ the Inbox shows are worked out by the daemon from the agent's own workspace,
905
+ never taken from the agent. The grant expires after 15 minutes, or sooner if
906
+ your org's `max_minutes` is shorter, even when the org shortens it later. It is
907
+ used up even if the push then fails. If nobody answers, or the
908
+ daemon isn't running, it stays denied.
909
+
910
+ **Remembered approvals** are saved in your user permission preferences, across
911
+ sessions and daemon restarts, until you revoke them in Settings → Permission
912
+ grants. A remembered push covers the same repository, remote URL and destination
913
+ branch across future commits; a file approval covers that specific file.
914
+ Every use still checks the current policy, explicit denies and adapter protections.
915
+ Temporary time/use limits apply to temporary grants; persistent approvals require
916
+ their own policy permission. Turning that permission off prevents their use.
917
+ - `git push` qualifies only as `git push <remote> <full commit sha>:refs/heads/<branch>`, run in the agent's own worktree (not another clone or a nested repository), and only when:
918
+ - the remote resolves (insteadOf applied) to exactly one https, ssh or `user@host:path` URL;
919
+ - none of `core.sshCommand`, `core.hooksPath`, the remote's receivepack/vcs/mirror, `push.followTags` or `push.recurseSubmodules` is set;
920
+ - there is no pre-push or reference-transaction hook;
921
+ - neither the repo nor your org sets protected paths (a push skips the merge check that enforces them).
922
+ - `.env*` qualifies for one file inside the worktree.
923
+
924
+ After your answer, brindle checks the policy, every rule and that scope again
925
+ before allowing. Anything that changed meanwhile refuses it. The command then
926
+ runs outside brindle.
927
+
928
+ Never lifted: your own, the repo's, a profile's or your org's denies, git force
929
+ flags, and reads of `~/.ssh`, `~/.aws`, `~/.gnupg` or the daemon's token.
930
+ Antigravity and OpenCode workers get no grants: agy ignores a hook's allow, and
931
+ OpenCode's plugin has no hold.
932
+
933
+ In a Team org, only the org policy's `permission_grants` block decides what may
934
+ be granted. It is read again on every request:
935
+
936
+ ```json
937
+ "permission_grants": {"allow": true, "kinds": ["bash", "fetch"], "overridable": ["git-push"],
938
+ "max_minutes": 30, "max_uses": 5, "allow_persistent": true,
939
+ "deny": ["bash prefix terraform apply"]}
940
+ ```
941
+
942
+ - `deny` entries are enforced for every provider and can't be lifted.
943
+ - `allow_persistent` permits remembered overrides of these two built-in denies; omitted or false allows only temporary overrides. This setting does not control ordinary saved allow rules. The admin policy editor is not delivered by this local engine change.
944
+ - A policy without the block, an unknown or malformed field (which makes the whole policy unusable), a policy that can't be read, or a paused or shut-down member: no grants.
945
+ - Without a Team org, any kind can be granted, for up to 60 min and 20 uses. Both built-in denies support one-time or remembered approvals.
946
+ - The org backend that serves this block isn't part of this repository. Until it serves one, Team orgs get no grants. It is enforced from the cached policy, or in air-gap mode from `.brindle/policy.json`.
947
+
884
948
  **Codex.** Codex runs a hook only once it's trusted, so with
885
949
  `permission_policy` on, brindle trusts its own hook the first time a Codex worker
886
950
  needs it (and says so at `brindle start`). It adds one entry,
@@ -2073,8 +2137,10 @@ folder; `brindle profile new` writes one.
2073
2137
  ---
2074
2138
  name: developer-bedrock
2075
2139
  provider: claude
2140
+ auth: cloud
2076
2141
  model: us.anthropic.claude-haiku-4-5-20251001-v1:0 # a Bedrock model id your account can use
2077
2142
  env.CLAUDE_CODE_USE_BEDROCK: 1
2143
+ env.AWS_PROFILE: company
2078
2144
  env.AWS_REGION: us-east-1
2079
2145
  ---
2080
2146
  ```
@@ -2101,10 +2167,125 @@ list it under `routing` in `.brindle/config.json`.
2101
2167
  `"modelProvider": "gemini"` in `~/.gemini/antigravity-cli/settings.json`, and
2102
2168
  export `GEMINI_API_KEY` in your shell, or put it in a profile in your user
2103
2169
  profiles folder; never in a profile under `.brindle/agents/`, which is committed. For a
2104
- Gemini-compatible gateway, set `env.GOOGLE_GEMINI_BASE_URL` to its URL. As of
2105
- 1.3.4, `agy` ignores Vertex AI credentials, so use a Gemini API key or gateway.
2170
+ Gemini-compatible gateway, set `env.GOOGLE_GEMINI_BASE_URL` to its URL.
2171
+ The Vertex login path in `agy` has not been validated by brindle; see the
2172
+ OpenCode alternative below for Gemini on Vertex.
2106
2173
  The full setup is in [Google Antigravity](#google-antigravity) below.
2107
2174
 
2175
+ For Claude on an existing AWS SSO profile or Google ADC, launch preflight checks
2176
+ the configured cloud login and offers its cloud sign-in instead of Claude's
2177
+ personal sign-in. The action keeps the profile's project, region and credential
2178
+ location through both desktop sign-in and the completion check; repeated presses
2179
+ reuse its running terminal. Custom credential files offer a manual refresh check.
2180
+ Desktop and automatic sign-in share the credential source's lock, so a second
2181
+ flow leaves an existing cloud login running.
2182
+ `brindle doctor` groups
2183
+ profiles by cloud identity and reports credential availability, expiry when
2184
+ known, and refresh instructions. AWS account and role are selected by the named
2185
+ SSO profile. To isolate Google ADC, set `env.CLOUDSDK_CONFIG` to that identity's
2186
+ gcloud directory alongside the Vertex project and region.
2187
+ Use `auth: cloud` to remove inherited model API keys, including Bedrock bearer
2188
+ keys that would otherwise bypass the AWS SSO profile.
2189
+
2190
+ `GOOGLE_APPLICATION_CREDENTIALS` selects a specific credential file. A browser
2191
+ ADC login writes the selected gcloud directory's default file. If the explicit
2192
+ credential file is elsewhere, brindle asks you to refresh it directly instead of
2193
+ opening a login that would update a different identity. Existing
2194
+ federated credentials and non-SSO AWS credential chains keep their existing
2195
+ handling; the new profile checks do not validate their expiry. Expired cloud
2196
+ credentials hold session delivery until refreshed, including when a cloud
2197
+ worker belongs to a Codex or other provider's supervisor. A session using several
2198
+ cloud identities stays held until all of them work again.
2199
+
2200
+ Company-cloud routes (#265):
2201
+
2202
+ | Model route | Agent and identity | Validation still needed |
2203
+ | --- | --- | --- |
2204
+ | Claude on Bedrock | Claude Code, AWS SSO profile and `AWS_REGION` | Live cloud validation |
2205
+ | Claude on Vertex | Claude Code, ADC, `ANTHROPIC_VERTEX_PROJECT_ID` and `CLOUD_ML_REGION` | Live cloud validation |
2206
+ | Gemini on Vertex | OpenCode, `auth: cloud`, `google-vertex/<model-id>`, project/region and ADC | Live cloud validation |
2207
+ | Azure OpenAI | OpenCode, `auth: cloud`, `azure/<deployment>`, Azure CLI and Entra connection | Live cloud validation |
2208
+
2209
+ For Gemini Vertex, put these fields in an agent profile (choose a model available
2210
+ in your project and region):
2211
+
2212
+ ```yaml
2213
+ provider: opencode
2214
+ model: google-vertex/gemini-2.5-pro
2215
+ auth: cloud
2216
+ env.GOOGLE_CLOUD_PROJECT: your-project
2217
+ env.VERTEX_LOCATION: us-central1
2218
+ env.CLOUDSDK_CONFIG: /absolute/path/to/company-gcloud
2219
+ ```
2220
+
2221
+ brindle passes that directory's `application_default_credentials.json` explicitly
2222
+ to OpenCode's SDK and checks the same file, while retaining browser refresh through
2223
+ gcloud. The profile's project and region override inherited OpenCode aliases.
2224
+ An explicit profile `CLOUDSDK_CONFIG` also overrides an inherited ADC file selector;
2225
+ set `env.GOOGLE_APPLICATION_CREDENTIALS` in the profile to select a custom file instead.
2226
+
2227
+ For Azure OpenAI, select a separate Azure CLI directory for this identity and
2228
+ your deployed model's name:
2229
+
2230
+ ```yaml
2231
+ provider: opencode
2232
+ model: azure/your-deployment
2233
+ auth: cloud
2234
+ env.AZURE_CONFIG_DIR: /absolute/path/to/company-azure
2235
+ env.AZURE_RESOURCE_NAME: your-resource
2236
+ env.AZURE_TENANT_ID: your-tenant
2237
+ env.AZURE_SUBSCRIPTION_ID: your-subscription
2238
+ ```
2239
+
2240
+ Azure's directory and resource are required. Tenant and subscription, when set,
2241
+ must match the CLI's active identity. The sign-in action runs `az login` for that
2242
+ identity when needed, then OpenCode's `auth login --provider azure --method
2243
+ "Microsoft Entra ID (Azure CLI)"`. It checks a Cognitive Services access token
2244
+ and the connection for the selected resource before allowing launch. It never
2245
+ switches the default Azure CLI configuration behind your back.
2246
+
2247
+ OpenCode cloud profiles receive an isolated connection store beneath
2248
+ `~/.brindle/cloud-opencode/` (or `BRINDLE_HOME`), keyed by their cloud identity
2249
+ and project/resource. `auth: cloud` removes inherited API keys that could select
2250
+ personal billing. Existing API-key profiles keep their behavior: a qualified
2251
+ model alone does not enable cloud login. Both cloud paths share profile-aware
2252
+ preflight/doctor checks, one in-flight sign-in per credential source, expiry
2253
+ holds, and live/paused-session recovery.
2254
+
2255
+ OpenCode's [provider documentation](https://opencode.ai/docs/providers/) describes
2256
+ both alternatives. For Azure, its Entra connection is a required setup step;
2257
+ an Azure endpoint alone does not select CLI authentication. This is Azure OpenAI,
2258
+ separate from Claude on Foundry. The installed CLI inspected for these options
2259
+ was OpenCode 1.19.3. brindle's native client can send bearer-authenticated OpenAI
2260
+ chat requests, but it does not yet acquire or renew Google/Azure CLI tokens.
2261
+ Those routes need token lifecycle support before native cloud login can be claimed.
2262
+
2263
+ Focused fake-based checks:
2264
+
2265
+ ```sh
2266
+ uv run pytest tests/test_daemon_actions.py -q -k 'cloud_profile_login or opencode_cloud_identity or login_and_secret or a_sign_in or preflight'
2267
+ uv run pytest tests/test_cloud_login.py -q
2268
+ uv run pytest tests/test_login_pause.py -q
2269
+ uv run pytest tests/test_doctor_signin.py -q
2270
+ uv run pytest tests/test_auth_failure_handling.py -q
2271
+ uv run pytest tests/test_opencode_provider.py -q
2272
+ uv run pytest tests/test_profile_auth.py -q
2273
+ npm --prefix app test -- --run src/screens/New/NewSession.test.tsx
2274
+ npm --prefix app test -- --run src/components/ProviderSignIn.test.tsx
2275
+ npm --prefix app run typecheck
2276
+ ```
2277
+
2278
+ `cloud_profile_login` and `opencode_cloud_identity` exercise real profile/environment
2279
+ resolution and HTTP login/preflight dispatch, identity propagation, duplicate
2280
+ actions, doctor expiry and live/paused credential-block recovery. The login-pause
2281
+ tests cover structured OpenCode expiry events, cross-provider supervisors,
2282
+ multiple expired identities and held-message delivery after recovery.
2283
+ Cloud commands and panes are fakes. No live AWS SSO, Google ADC, Azure login,
2284
+ model inference, or user cloud-config changes were used for validation. Provider
2285
+ permissions, deployed model availability, browser flows and token renewal against
2286
+ real cloud services still need manual validation. Gemini/Azure use OpenCode;
2287
+ native SDK and Antigravity cloud integration are not added by this change.
2288
+
2108
2289
  If your org runs brindle for a team, its admins can set a route for everyone in
2109
2290
  the org's Routing settings on the brindle dashboard; an enforced org route replaces
2110
2291
  a profile's `env.` lines, so a profile can't move a worker off the company's route. See
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brindle"
3
- version = "0.4.4"
3
+ version = "0.4.6"
4
4
  description = "Run coding agents in parallel, each on its own git branch, with a supervisor that delegates, reviews, and merges"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -26,6 +26,7 @@ dependencies = [
26
26
  "typer>=0.12",
27
27
  "mcp>=2.2,<3",
28
28
  "cryptography>=42",
29
+ "wsproto>=1.2",
29
30
  ]
30
31
 
31
32
  [project.urls]