Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ ucode configure --workspaces https://first.databricks.com,https://second.databri

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.

Alternatively, pass existing Databricks CLI profiles (from `~/.databrickscfg`) instead of workspace URLs — each profile's host supplies the workspace URL:

```bash
ucode configure --profiles DEFAULT --agents claude,codex
```

Auth behaves the same as `--workspaces`: an OAuth `databricks auth login` is forced by default.

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:

```bash
ucode configure --profiles DEFAULT --agents claude,codex --use-pat --skip-validate --skip-upgrade
```

### MCP servers (optional)

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

## Managed Local Files

Expand Down
7 changes: 6 additions & 1 deletion src/ucode/agents/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def render_overlay(
claude_models: dict[str, str] | None = None,
disable_web_search: bool = False,
profile: str | None = None,
use_pat: bool = False,
) -> tuple[dict, list[list[str]]]:
"""Return (overlay, managed_key_paths) for Claude settings.json.

Expand Down Expand Up @@ -147,7 +148,10 @@ def render_overlay(
env["ANTHROPIC_DEFAULT_SONNET_MODEL"] = _maybe_add_1m_suffix(claude_models["sonnet"])
if claude_models.get("haiku"):
env["ANTHROPIC_DEFAULT_HAIKU_MODEL"] = claude_models["haiku"]
overlay: dict = {"apiKeyHelper": build_auth_shell_command(workspace, profile), "env": env}
overlay: dict = {
"apiKeyHelper": build_auth_shell_command(workspace, profile, use_pat=use_pat),
"env": env,
}
keys: list[list[str]] = [["apiKeyHelper"]] + [["env", k] for k in env]

# Disable Claude Code's built-in WebSearch (it routes through Anthropic's
Expand Down Expand Up @@ -226,6 +230,7 @@ def write_tool_config(state: dict, model: str) -> dict:
state.get("claude_models") or {},
disable_web_search=web_search_model is not None,
profile=state.get("profile"),
use_pat=bool(state.get("use_pat")),
)
tracing_env_vars = tracing_env(state, "claude")
stop_hook_command = claude_tracing_stop_hook_command() if tracing_env_vars else None
Expand Down
26 changes: 18 additions & 8 deletions src/ucode/agents/codex.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ def _use_legacy_layout() -> bool:
return parsed < MINIMUM_CODEX_VERSION


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


def render_overlay(
workspace: str, model: str | None = None, databricks_profile: str | None = None
workspace: str,
model: str | None = None,
databricks_profile: str | None = None,
use_pat: bool = False,
) -> dict:
overlay: dict = {"model_provider": CODEX_MODEL_PROVIDER_NAME}
if model:
overlay["model"] = model
overlay["model_providers"] = {
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile),
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile, use_pat),
}
return overlay


def render_legacy_overlay(
workspace: str, model: str | None = None, databricks_profile: str | None = None
workspace: str,
model: str | None = None,
databricks_profile: str | None = None,
use_pat: bool = False,
) -> dict:
"""Overlay for Codex CLI < 0.134.0, which only reads `~/.codex/config.toml`.

Expand All @@ -147,7 +153,7 @@ def render_legacy_overlay(
"profile": CODEX_PROFILE_NAME,
"profiles": {CODEX_PROFILE_NAME: profile_block},
"model_providers": {
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile),
CODEX_MODEL_PROVIDER_NAME: _provider_block(workspace, databricks_profile, use_pat),
},
}

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

_remove_legacy_ucode_profile()
backup_existing_file(CODEX_CONFIG_PATH, CODEX_BACKUP_PATH)
overlay = render_overlay(workspace, chosen_model, databricks_profile)
overlay = render_overlay(
workspace, chosen_model, databricks_profile, use_pat=bool(state.get("use_pat"))
)
doc = read_toml_safe(CODEX_CONFIG_PATH)
deep_merge_dict(doc, overlay)
write_toml_file(CODEX_CONFIG_PATH, doc)
Expand Down
Loading
Loading