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.
- {brindle-0.4.4 → brindle-0.4.6}/.gitignore +2 -1
- {brindle-0.4.4 → brindle-0.4.6}/PKG-INFO +185 -3
- {brindle-0.4.4 → brindle-0.4.6}/README.md +183 -2
- {brindle-0.4.4 → brindle-0.4.6}/pyproject.toml +2 -1
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/agents.py +741 -137
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/antigravity.py +54 -8
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/autopilot.py +184 -46
- brindle-0.4.6/src/brindle/bus.py +75 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cli.py +222 -359
- brindle-0.4.6/src/brindle/clone.py +83 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cloud_login.py +220 -12
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/codex_hook.py +73 -15
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/company_identity.py +21 -15
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/company_login.py +21 -7
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/config.py +120 -7
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/coordinator.py +11 -5
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cull.py +30 -13
- brindle-0.4.6/src/brindle/daemon/__init__.py +6 -0
- brindle-0.4.6/src/brindle/daemon/api-schema.json +5912 -0
- brindle-0.4.6/src/brindle/daemon/app.py +52 -0
- brindle-0.4.6/src/brindle/daemon/auth.py +146 -0
- brindle-0.4.6/src/brindle/daemon/events.py +203 -0
- brindle-0.4.6/src/brindle/daemon/grants.py +320 -0
- brindle-0.4.6/src/brindle/daemon/holds.py +318 -0
- brindle-0.4.6/src/brindle/daemon/lifecycle.py +310 -0
- brindle-0.4.6/src/brindle/daemon/peer.py +116 -0
- brindle-0.4.6/src/brindle/daemon/routes_actions.py +1788 -0
- brindle-0.4.6/src/brindle/daemon/routes_org_permissions.py +27 -0
- brindle-0.4.6/src/brindle/daemon/routes_permissions.py +37 -0
- brindle-0.4.6/src/brindle/daemon/routes_read.py +584 -0
- brindle-0.4.6/src/brindle/daemon/schema.py +58 -0
- brindle-0.4.6/src/brindle/daemon/session_review.py +470 -0
- brindle-0.4.6/src/brindle/daemon/terminal.py +135 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/db.py +239 -38
- brindle-0.4.6/src/brindle/debuglog.py +410 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/doctor.py +97 -10
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/events.py +16 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/gates.py +4 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/git.py +27 -9
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/key_check.py +2 -1
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/keys_cmds.py +30 -12
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/knowledge.py +13 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/launch_health.py +25 -7
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/login_pause.py +64 -12
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/mcp_server.py +69 -85
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/loop.py +5 -1
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/permissions.py +8 -3
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/runner.py +50 -1
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/opencode.py +8 -3
- brindle-0.4.6/src/brindle/org_permissions.py +145 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pane_auth.py +7 -2
- brindle-0.4.6/src/brindle/permission_editor.py +350 -0
- brindle-0.4.6/src/brindle/permission_grants.py +646 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/permissions.py +606 -48
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/picker.py +17 -4
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pipeline.py +93 -23
- brindle-0.4.6/src/brindle/preflight.py +291 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/account.py +25 -14
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/team_policy.py +145 -29
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/profiles.py +58 -1
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/prompt_answer.py +82 -30
- brindle-0.4.6/src/brindle/provider_homes.py +287 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/providers.py +215 -27
- brindle-0.4.6/src/brindle/providers_install.py +268 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/quota.py +3 -1
- brindle-0.4.6/src/brindle/service/__init__.py +4 -0
- brindle-0.4.6/src/brindle/service/dto.py +537 -0
- brindle-0.4.6/src/brindle/service/errors.py +60 -0
- brindle-0.4.6/src/brindle/service/inbox.py +502 -0
- brindle-0.4.6/src/brindle/service/routing.py +119 -0
- brindle-0.4.6/src/brindle/service/sessions.py +509 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/sessions.py +47 -7
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/sidebar_follow.py +81 -8
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/status_cache.py +50 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/tasks.py +252 -63
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/tmux.py +140 -12
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/upgrade.py +36 -8
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/view.py +65 -3
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/watch.py +107 -71
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/workspaces.py +52 -15
- {brindle-0.4.4 → brindle-0.4.6}/LICENSE +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/SCHEDULE-A +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/__init__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/__main__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/account.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/airgap.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/assembler.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/assets/braid_dark.png +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/assets/braid_light.png +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/budget.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/__init__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/backend-auditor.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/coordinator.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-codex.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-fable.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-gemini.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-heavy.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-light.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-local.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-opencode.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer-sonnet.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/developer.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/qa.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-codex.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-gemini.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-local.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer-opencode.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/reviewer.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/subagent.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_agents/supervisor.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/__init__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/security/backend.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/style/minimal-diff.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/builtin_rules/tests/only.md +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_adapters.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_client.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_federation.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/ci_hosts.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cloud_credential.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/codemap.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/codemap_docs.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/conflicts.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cost.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/cost_estimate.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/demo.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/detect.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/escapes.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/guardrails.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/history.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/identity_lock.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/inbox.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/kb.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/keystore.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/learned_rules.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/learning.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/legacy.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/local_setup.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/model_access.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/model_availability.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/__init__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/catalog.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/client.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/lsp.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/serve.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/native/tools.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/plugins.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/policy.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pool.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pricing.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/__init__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/_files.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/audit_chain.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/audit_export.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/auth.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/brief_learning.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/cost_centers.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/credentials.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/features.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/keys.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/learning.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/license.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/loopback.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/managed_models.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/org_profiles.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/orgkey.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/rollout.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/route_health.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/settings_sync.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/status.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/pro/team_events.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/procs.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/repo_cmds.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/repos.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/rewind.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/rule_checks.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/savings.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/scratch.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/secrets.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/self_serve.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/services.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/sidebar_logo.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/signin_pause.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/updates.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/usage.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/wake.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/watch_costs.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/__init__.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/chat.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/dashboard.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/diff.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/server.py +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/static/app.js +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/web/static/index.html +0 -0
- {brindle-0.4.4 → brindle-0.4.6}/src/brindle/worker_models.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brindle
|
|
3
|
-
Version: 0.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.
|
|
2136
|
-
|
|
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.
|
|
2105
|
-
|
|
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.
|
|
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]
|