Skip to content

Commit efaec90

Browse files
committed
Support non-interactive configure with --profiles, --use-pat, and --skip-validate
Adds an explicit, fully non-interactive setup path for CI / headless environments (e.g. lakebox sandboxes provisioned with a PAT-backed DEFAULT profile): - `configure --profiles <names>` resolves workspace URLs from existing ~/.databrickscfg profiles instead of prompting. Auth behaves like --workspaces: OAuth login is forced by default. - `--use-pat` (requires --profiles) authenticates with the profile's personal access token instead of OAuth — ucode never picks up a PAT implicitly. The token is validated, exported as DATABRICKS_BEARER for the configure run and launched agents, and persisted as use_pat in state so launches inherit the mode; written agent configs carry a `databricks auth describe --sensitive`-based auth command so bare claude/codex runs refresh from the PAT too. - `--skip-validate` skips the post-configure test message through each agent; configs are still written with freshly discovered models. Co-authored-by: Isaac Signed-off-by: Bryan Qiu <bryan.qiu@databricks.com>
1 parent 9d97390 commit efaec90

12 files changed

Lines changed: 716 additions & 20 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,20 @@ ucode configure --workspaces https://first.databricks.com,https://second.databri
6161

6262
When multiple workspaces are provided, `ucode` logs into and saves state for each workspace. Launch commands such as `ucode codex` use the first workspace in the list.
6363

64+
Alternatively, pass existing Databricks CLI profiles (from `~/.databrickscfg`) instead of workspace URLs — each profile's host supplies the workspace URL:
65+
66+
```bash
67+
ucode configure --profiles DEFAULT --agents claude,codex
68+
```
69+
70+
Auth behaves the same as `--workspaces`: an OAuth `databricks auth login` is forced by default.
71+
72+
For CI or headless environments where the profile holds a personal access token (`auth_type = pat` in `~/.databrickscfg`), add `--use-pat`. It must be combined with `--profiles` — ucode never picks up a PAT implicitly — and runs no interactive login: the profile's token is used for the whole setup (and by launched agents afterwards), with workspace access verified against the AI Gateway. `--skip-validate` additionally skips the post-configure test message sent through each agent, so configure only writes config files with the freshly discovered models. Together these make setup fully non-interactive:
73+
74+
```bash
75+
ucode configure --profiles DEFAULT --agents claude,codex --use-pat --skip-validate --skip-upgrade
76+
```
77+
6478
### MCP servers (optional)
6579

6680
```bash
@@ -90,6 +104,9 @@ Discovered external MCP connections are listed directly. MCP auth uses a Databri
90104
| `ucode configure --dry-run` | Preview config files without writing them |
91105
| `ucode configure --agents claude,codex` | Configure specific agents without the interactive picker |
92106
| `ucode configure --workspaces https://first.databricks.com,https://second.databricks.com` | Configure workspaces without the interactive picker |
107+
| `ucode configure --profiles DEFAULT` | Configure using existing Databricks CLI profiles (hosts come from `~/.databrickscfg`) |
108+
| `ucode configure --profiles DEFAULT --use-pat` | Authenticate with the profile's personal access token — no browser login |
109+
| `ucode configure --skip-validate` | Write configs without sending a test message through each agent |
93110

94111
## Managed Local Files
95112

src/ucode/agents/claude.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ def render_overlay(
116116
claude_models: dict[str, str] | None = None,
117117
disable_web_search: bool = False,
118118
profile: str | None = None,
119+
use_pat: bool = False,
119120
) -> tuple[dict, list[list[str]]]:
120121
"""Return (overlay, managed_key_paths) for Claude settings.json.
121122
@@ -147,7 +148,10 @@ def render_overlay(
147148
env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = _maybe_add_1m_suffix(claude_models["sonnet"])
148149
if claude_models.get("haiku"):
149150
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = claude_models["haiku"]
150-
overlay: dict = {"apiKeyHelper": build_auth_shell_command(workspace, profile), "env": env}
151+
overlay: dict = {
152+
"apiKeyHelper": build_auth_shell_command(workspace, profile, use_pat=use_pat),
153+
"env": env,
154+
}
151155
keys: list[list[str]] = [["apiKeyHelper"]] + [["env", k] for k in env]
152156

153157
# Disable Claude Code's built-in WebSearch (it routes through Anthropic's
@@ -226,6 +230,7 @@ def write_tool_config(state: dict, model: str) -> dict:
226230
state.get("claude_models") or {},
227231
disable_web_search=web_search_model is not None,
228232
profile=state.get("profile"),
233+
use_pat=bool(state.get("use_pat")),
229234
)
230235
tracing_env_vars = tracing_env(state, "claude")
231236
stop_hook_command = claude_tracing_stop_hook_command() if tracing_env_vars else None

src/ucode/agents/codex.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ def _use_legacy_layout() -> bool:
101101
return parsed < MINIMUM_CODEX_VERSION
102102

103103

104-
def _provider_block(workspace: str, databricks_profile: str | None) -> dict:
105-
auth_command = build_auth_shell_command(workspace, databricks_profile)
104+
def _provider_block(workspace: str, databricks_profile: str | None, use_pat: bool = False) -> dict:
105+
auth_command = build_auth_shell_command(workspace, databricks_profile, use_pat=use_pat)
106106
base_url = build_tool_base_url("codex", workspace)
107107
return {
108108
"name": "Databricks AI Gateway",
@@ -121,19 +121,25 @@ def _provider_block(workspace: str, databricks_profile: str | None) -> dict:
121121

122122

123123
def render_overlay(
124-
workspace: str, model: str | None = None, databricks_profile: str | None = None
124+
workspace: str,
125+
model: str | None = None,
126+
databricks_profile: str | None = None,
127+
use_pat: bool = False,
125128
) -> dict:
126129
overlay: dict = {"model_provider": CODEX_MODEL_PROVIDER_NAME}
127130
if model:
128131
overlay["model"] = model
129132
overlay["model_providers"] = {
130-
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile),
133+
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile, use_pat),
131134
}
132135
return overlay
133136

134137

135138
def render_legacy_overlay(
136-
workspace: str, model: str | None = None, databricks_profile: str | None = None
139+
workspace: str,
140+
model: str | None = None,
141+
databricks_profile: str | None = None,
142+
use_pat: bool = False,
137143
) -> dict:
138144
"""Overlay for Codex CLI < 0.134.0, which only reads `~/.codex/config.toml`.
139145
@@ -147,7 +153,7 @@ def render_legacy_overlay(
147153
"profile": CODEX_PROFILE_NAME,
148154
"profiles": {CODEX_PROFILE_NAME: profile_block},
149155
"model_providers": {
150-
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile),
156+
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile, use_pat),
151157
},
152158
}
153159

@@ -295,7 +301,9 @@ def write_tool_config(state: dict, model: str | None = None) -> dict:
295301
# and skip the per-profile-file cleanup that would normally strip
296302
# ucode's entry from the shared file.
297303
backup_existing_file(LEGACY_CODEX_CONFIG_PATH, LEGACY_CODEX_BACKUP_PATH)
298-
overlay = render_legacy_overlay(workspace, chosen_model, databricks_profile)
304+
overlay = render_legacy_overlay(
305+
workspace, chosen_model, databricks_profile, use_pat=bool(state.get("use_pat"))
306+
)
299307
doc = read_toml_safe(LEGACY_CODEX_CONFIG_PATH)
300308
deep_merge_dict(doc, overlay)
301309
write_toml_file(LEGACY_CODEX_CONFIG_PATH, doc)
@@ -305,7 +313,9 @@ def write_tool_config(state: dict, model: str | None = None) -> dict:
305313

306314
_remove_legacy_ucode_profile()
307315
backup_existing_file(CODEX_CONFIG_PATH, CODEX_BACKUP_PATH)
308-
overlay = render_overlay(workspace, chosen_model, databricks_profile)
316+
overlay = render_overlay(
317+
workspace, chosen_model, databricks_profile, use_pat=bool(state.get("use_pat"))
318+
)
309319
doc = read_toml_safe(CODEX_CONFIG_PATH)
310320
deep_merge_dict(doc, overlay)
311321
write_toml_file(CODEX_CONFIG_PATH, doc)

0 commit comments

Comments
 (0)