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
35 changes: 16 additions & 19 deletions docs/components/quickstartShared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,16 @@ export const AGENT = {
} as const

export const OS_PREREQS: Record<OS, string> = {
mac: `# Homebrew — runtime, Git, provider CLIs, Node (for the agent CLI)
brew install uv git node glab gh`,
mac: `# Homebrew — runtime, Git, Node (for the agent CLI)
brew install uv git node`,
linux: `# uv (Python 3.14+ toolchain)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Debian/Ubuntu — Git, Node, GitHub CLI
sudo apt-get update && sudo apt-get install -y git nodejs npm gh

# GitLab CLI (glab): https://gitlab.com/gitlab-org/cli#installation`,
# Debian/Ubuntu — Git, Node
sudo apt-get update && sudo apt-get install -y git nodejs npm`,
windows: `# PowerShell
irm https://astral.sh/uv/install.ps1 | iex
winget install Git.Git OpenJS.NodeJS GitHub.cli`,
winget install Git.Git OpenJS.NodeJS`,
}

export const METHOD: Record<Method, string> = {
Expand Down Expand Up @@ -237,8 +235,8 @@ provider = "${scm}"
token = "$env:${s.env}"

[agents]
llm_model = "${a.model}"
llm_api_key_env = "${a.keyEnv}"${reviewerPwsh}
llm_model = "${a.model}"
llm_api_key = "$env:${a.keyEnv}"${reviewerPwsh}

[[projects]]
path = "$PROJECT"
Expand Down Expand Up @@ -292,8 +290,8 @@ provider = "${scm}"
token = "\${${s.env}}"

[agents]
llm_model = "${a.model}"
llm_api_key_env = "${a.keyEnv}"${reviewer}
llm_model = "${a.model}"
llm_api_key = "\${${a.keyEnv}}"${reviewer}

[[projects]]
path = "\${PROJECT}"
Expand All @@ -319,7 +317,7 @@ $PROJECT = "${s.path}"

# 1) prerequisites + ${a.cliName} CLI
irm https://astral.sh/uv/install.ps1 | iex
winget install Git.Git OpenJS.NodeJS GitHub.cli
winget install Git.Git OpenJS.NodeJS
${a.cli}

# 2) install bubo
Expand All @@ -336,8 +334,8 @@ provider = "${scm}"
token = "$env:${s.env}"

[agents]
llm_model = "${a.model}"
llm_api_key_env = "${a.keyEnv}"${reviewerPwsh}
llm_model = "${a.model}"
llm_api_key = "$env:${a.keyEnv}"${reviewerPwsh}

[[projects]]
path = "$PROJECT"
Expand All @@ -351,10 +349,9 @@ bubo-poller`

const prereq =
os === 'mac'
? 'brew install uv git node glab gh'
? 'brew install uv git node'
: `curl -LsSf https://astral.sh/uv/install.sh | sh
sudo apt-get update && sudo apt-get install -y git nodejs npm gh
# glab (GitLab CLI): https://gitlab.com/gitlab-org/cli#installation`
sudo apt-get update && sudo apt-get install -y git nodejs npm`
const reviewer = agent === 'claude' ? `\nreviewer_command = ["claude", "-p"]` : ''
return `#!/usr/bin/env bash
set -euo pipefail
Expand Down Expand Up @@ -382,8 +379,8 @@ provider = "${scm}"
token = "\${${s.env}}"

[agents]
llm_model = "${a.model}"
llm_api_key_env = "${a.keyEnv}"${reviewer}
llm_model = "${a.model}"
llm_api_key = "\${${a.keyEnv}}"${reviewer}

[[projects]]
path = "\${PROJECT}"
Expand Down
2 changes: 1 addition & 1 deletion docs/components/recipes/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ export const claude: AgentFragment = {
cliName: a.cliName,
configBlock: `[agents]
llm_model = "${a.model}"
llm_api_key_env = "${a.keyEnv}"
llm_api_key = "\${${a.keyEnv}}"
reviewer_command = ["claude", "-p"] # Claude is driven via its CLI`,
}
4 changes: 2 additions & 2 deletions docs/components/recipes/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export const codex: AgentFragment = {
cli: a.cli,
cliName: a.cliName,
configBlock: `[agents]
llm_model = "${a.model}"
llm_api_key_env = "${a.keyEnv}" # env var the ${a.cliName} CLI reads`,
llm_model = "${a.model}"
llm_api_key = "\${${a.keyEnv}}" # your ${a.keyName} key, exported as LLM_API_KEY`,
}
2 changes: 1 addition & 1 deletion docs/components/recipes/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ enabled = true`

const gatewayLine =
agent === 'selfhosted'
? '\n# The OpenAI-compatible endpoint URL.\ngateway_base_url = "<https://llm.corp.internal/v1>"'
? '\n# The OpenAI-compatible endpoint URL.\nllm_base_url = "<https://llm.corp.internal/v1>"'
: ''
const srcKind = install === 'docker' ? 'registry' : 'index'
const sourceValue = install === 'docker' ? 'ghcr.io/mountainowl/bubo' : 'https://pypi.org/project/bubo/'
Expand Down
7 changes: 2 additions & 5 deletions docs/components/recipes/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ const s = SCM.github

export const github: ScmFragment = {
prereq: {
text: 'gh (GitHub CLI), authenticated — clones and fetches pull requests',
links: [
{ label: 'gh', href: 'https://cli.github.com/' },
{ label: 'token', href: s.tokenUrl },
],
text: 'A GitHub token with pull-request read + write — clones over HTTPS and drives the REST API',
links: [{ label: 'token', href: s.tokenUrl }],
},
projectPath: s.path,
configBlock: `[scm]
Expand Down
7 changes: 2 additions & 5 deletions docs/components/recipes/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ const s = SCM.gitlab

export const gitlab: ScmFragment = {
prereq: {
text: 'glab (GitLab CLI), authenticated — clones and fetches merge requests',
links: [
{ label: 'glab', href: 'https://gitlab.com/gitlab-org/cli#installation' },
{ label: 'token', href: s.tokenUrl },
],
text: 'A GitLab token with API scope — clones over HTTPS and drives the REST API',
links: [{ label: 'token', href: s.tokenUrl }],
},
projectPath: s.path,
configBlock: `[scm]
Expand Down
5 changes: 2 additions & 3 deletions docs/components/recipes/linux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ export const linux: OsFragment = {
prereqInstall: (cli) => `# uv (Python 3.14+ toolchain)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Debian/Ubuntu — Git, Node, GitHub CLI
sudo apt-get update && sudo apt-get install -y git nodejs npm gh
# glab (GitLab CLI): https://gitlab.com/gitlab-org/cli#installation
# Debian/Ubuntu — Git, Node
sudo apt-get update && sudo apt-get install -y git nodejs npm
${cli}`,
exportLine: (name, ph) => `export ${name}="${ph}"`,
}
4 changes: 2 additions & 2 deletions docs/components/recipes/mac.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export const mac: OsFragment = {
icon: '/apple.svg',
shell: 'bash',
configPath: '~/.local/share/bubo/config/env.toml',
prereqInstall: (cli) => `# Homebrew — runtime, Git, provider CLIs, Node (for the agent CLI)
brew install uv git node glab gh
prereqInstall: (cli) => `# Homebrew — runtime, Git, Node (for the agent CLI)
brew install uv git node
${cli}`,
exportLine: (name, ph) => `export ${name}="${ph}"`,
}
34 changes: 15 additions & 19 deletions docs/components/recipes/selfhosted.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { AGENT } from '../quickstartShared'
import type { AgentFragment } from './types'

// Self-hosted / OpenAI-compatible: Bubo still shells out to Codex, but the agent
// CLI is pointed at your own gateway (Azure OpenAI, vLLM/TGI, LiteLLM, internal
// proxy). The endpoint lives in the *Codex profile*, not env.toml — so this
// fragment carries an `extra` block for that.
// Self-hosted / OpenAI-compatible: Bubo still shells out to Codex, but points the
// agent at your own gateway (Azure OpenAI, vLLM/TGI, LiteLLM, internal proxy) via
// `llm_base_url` in env.toml. `bubo init` templates the matching model-provider
// block into the Codex profile, and the agent reads LLM_API_KEY from its
// environment at request time.
export const selfhosted: AgentFragment = {
prereq: {
text: 'Codex CLI (Bubo shells out to it) and an OpenAI-compatible gateway — Azure OpenAI, vLLM/TGI, LiteLLM, or an internal proxy',
Expand All @@ -18,22 +19,17 @@ export const selfhosted: AgentFragment = {
cli: AGENT.codex.cli,
cliName: 'Codex',
configBlock: `[agents]
llm_model = "<internal-model-name>" # matches the profile; cost label only
llm_api_key = "\${LLM_API_KEY}" # Bubo re-exports it under llm_api_key_env
llm_api_key_env = "OPENAI_API_KEY" # the env var your gateway/CLI reads
codex_profile = "bubo"`,
llm_model = "<internal-model-name>" # your gateway's model id; also the cost label
llm_api_key = "\${LLM_API_KEY}" # read from the environment at request time
llm_base_url = "https://llm.corp.internal/v1" # your OpenAI-compatible endpoint
codex_profile = "bubo"`,
extra: {
intro:
'Point the agent at your gateway — add a model-provider to the Codex profile (config.toml in the agent home: ~/.codex/ for a local install, the mounted home for Docker):',
code: `[profiles.bubo]
model = "<internal-model-name>"
model_provider = "inhouse"
sandbox_mode = "read-only"

[model_providers.inhouse]
name = "In-house gateway"
base_url = "https://llm.corp.internal/v1" # your OpenAI-compatible endpoint
env_key = "OPENAI_API_KEY"
wire_api = "chat" # "chat" or "responses" — match your gateway`,
'With `llm_base_url` set, `bubo init` writes this model-provider block into the Codex profile (config.toml in the agent home: ~/.codex/ for a local install, the mounted home for Docker) — no manual editing needed:',
code: `[model_providers.bubo]
name = "bubo custom endpoint"
base_url = "https://llm.corp.internal/v1" # from llm_base_url
env_key = "LLM_API_KEY"
wire_api = "chat"`,
},
}
53 changes: 34 additions & 19 deletions docs/pages/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ Required tools:
|---|---|
| Python 3.14+ | Bubo runtime. |
| Node.js | Codex or Claude CLI installs. |
| `git` | Repository checkout. |
| `glab` | GitLab checkout and GitLab tooling. |
| `gh` | GitHub checkout and GitHub tooling. |
| `git` | Repository checkout over tokenized HTTPS. |
| Codex, Claude, or another CLI agent | Review execution. |

## `[scm]`
Expand All @@ -54,20 +52,19 @@ Used when `[scm].provider = "gitlab"`.
| Key | Type | Default | Description |
|---|---|---|---|
| `url` | string | `"https://gitlab.com"` | GitLab web URL. Use your self-hosted GitLab URL when needed. |
| `api_url` | string | `"<url>/api/v4"` | GitLab REST API URL used by GitLab tools. |
| `api_url` | string | `"<url>/api/v4"` | GitLab REST API URL for MRs, diffs, and outcomes. |
| `bot_username` | string | unset | Bot username used to separate bot comments from developer replies. |
| `denied_tools_regex` | string | `"^(delete_.*\|merge_merge_request\|push_files)$"` | GitLab MCP tool names blocked inside the review agent. |
| `token` | string | unset | GitLab token. Exported as `GITLAB_TOKEN`, `GITLAB_PERSONAL_ACCESS_TOKEN`, and `GLAB_TOKEN`. Requires `api` scope. |
| `token` | string | unset | GitLab token, `api` scope. Exported as `GITLAB_TOKEN`, `GITLAB_PERSONAL_ACCESS_TOKEN`, and `GLAB_TOKEN`, and used as the credential for the HTTPS `git clone` — sent as a per-call auth header, never written to `.git/config`. |

## `[github]`

Used when `[scm].provider = "github"`.

| Key | Type | Default | Description |
|---|---|---|---|
| `api_url` | string | `"https://api.github.com"` | GitHub REST API base. Use `https://<host>/api/v3` for GitHub Enterprise Server. |
| `api_url` | string | `"https://api.github.com"` | GitHub REST API base. Use `https://<host>/api/v3` for GitHub Enterprise Server. The clone host is derived from it (`api.github.com` → `github.com`; GHES → its own host). |
| `bot_username` | string | unset | Bot username used to separate bot comments from developer replies. |
| `token` | string | unset | GitHub token. Exported as `GITHUB_TOKEN`, `GITHUB_PERSONAL_ACCESS_TOKEN`, and `GH_TOKEN`. Requires pull-request read/write access. |
| `token` | string | unset | GitHub token with pull-request read/write access. Exported as `GITHUB_TOKEN`, `GITHUB_PERSONAL_ACCESS_TOKEN`, and `GH_TOKEN`, and used as the credential for the HTTPS `git clone` — sent as a per-call auth header, never written to `.git/config`. |

## `[review]`

Expand Down Expand Up @@ -132,10 +129,11 @@ Controls the review agent command and agent-facing environment.
| Key | Type | Default | Description |
|---|---|---|---|
| `reviewer_command` | string array | `["codex", "--ask-for-approval", "never", "exec", "--profile", "bubo", "--skip-git-repo-check"]` | Command prefix used to run the review agent. The review prompt is appended as the final argument. |
| `llm_model` | string | unset | Model label used for review metadata and cost attribution. |
| `llm_api_key` | string | unset | LLM API key. Exported as `LLM_API_KEY` and optionally as `llm_api_key_env`. |
| `llm_api_key_env` | string | unset | Provider-specific env var name, such as `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, or `GEMINI_API_KEY`. |
| `reasoning_effort` | string | `"medium"` | Exported as `REVIEW_REASONING_EFFORT` for agents that honor it. |
| `llm_model` | string | `"gpt-5.5"` | Review model. Exported as `LLM_MODEL`; `bubo init` templates it into the agent profile (so it actually drives the model) and the `model` metric label reads it. Re-run `bubo init` after changing. |
| `llm_model_effort` | string | `"medium"` | Reasoning effort — `"low"`, `"medium"`, or `"high"`. Exported as `LLM_MODEL_EFFORT`; `bubo init` templates it into the agent profile. Falls back to the deprecated `reasoning_effort` key. |
| `llm_api_key` | string | unset | LLM API key, exported as `LLM_API_KEY`. By default the review agent authenticates via its own login (written by `bubo init`), so the key is **not** injected into the agent environment. The one exception is `llm_base_url`, which reads the key from the environment at request time. |
| `llm_base_url` | string | unset | Optional OpenAI-compatible endpoint (in-house gateway, proxy, local server). When set, `bubo init` points the Codex profile at it via a `[model_providers]` block, and the agent reads `LLM_API_KEY` from its environment. Exported as `LLM_BASE_URL`. Security: this is the one mode that exposes the key to the agent — leave unset unless you need it. |
| `llm_api_key_env` | string | unset | **Deprecated.** Named an extra env var to expose the key under; the agent now authenticates via its own login. Still honored when set, but prefer removing it. |
| `dry_run` | boolean | `true` | Exported as `REVIEW_DRY_RUN` for the agent. Posting is controlled by `[review].dry_run`. |
| `codex_profile` | string | `"bubo"` | Exported as `CODEX_REVIEW_PROFILE`. Used by Codex-based reviewer commands. |
| `codex_sandbox` | string | `"read-only"` | Exported as `CODEX_SANDBOX`. Used by Codex-based reviewer commands. |
Expand Down Expand Up @@ -163,6 +161,21 @@ Controls OpenTelemetry and cost estimation.
| `output_per_1m` | number | `0.0` | Estimated output-token price per 1M tokens. |
| `cached_input_per_1m` | number | `0.0` | Estimated cached-input price per 1M tokens. |

## `[analytics]`

Anonymous, aggregate usage analytics — on by default, opt-out. No code, diffs,
findings, tokens, repo names, or paths are ever sent.

| Key | Type | Default | Description |
|---|---|---|---|
| `enabled` | boolean | `true` | Send anonymous usage counts. Set `false` to opt out. |
| `endpoint` | string | built-in | OTLP log-ingestion endpoint. Blank disables sending. |
| `api_key` | string | built-in | Project key for the endpoint. Blank disables sending. |

Two environment kill-switches override the config: `BUBO_ANALYTICS=0` (also
`false`/`no`/`off`) and the cross-tool `DO_NOT_TRACK=1` convention. Either one
disables analytics regardless of `enabled`.

## `[mcp_server]`

Controls the `bubo-mcp` server. See [MCP server](/mcp) for client setup.
Expand Down Expand Up @@ -221,11 +234,10 @@ SQLite state. Change only the provider block and project paths for GitHub.
provider = "gitlab"

[gitlab]
url = "https://gitlab.com"
api_url = "https://gitlab.com/api/v4"
bot_username = "bubo"
denied_tools_regex = "^(delete_.*|merge_merge_request|push_files)$"
token = "${GITLAB_TOKEN}"
url = "https://gitlab.com"
api_url = "https://gitlab.com/api/v4"
bot_username = "bubo"
token = "${GITLAB_TOKEN}"

[github]
api_url = "https://api.github.com"
Expand Down Expand Up @@ -279,9 +291,9 @@ reviewer_command = [
"--skip-git-repo-check",
]
llm_model = "gpt-5.5"
llm_model_effort = "medium"
llm_api_key = "${LLM_API_KEY}"
llm_api_key_env = "OPENAI_API_KEY"
reasoning_effort = "medium"
# llm_base_url = "https://llm.internal.example/v1" # OpenAI-compatible endpoint
dry_run = true
codex_profile = "bubo"
codex_sandbox = "read-only"
Expand All @@ -301,6 +313,9 @@ input_per_1m = 0.0
output_per_1m = 0.0
cached_input_per_1m = 0.0

[analytics]
# enabled = false # anonymous usage analytics are on by default; uncomment to opt out

[mcp_server]
transport = "stdio"
host = "127.0.0.1"
Expand Down
10 changes: 4 additions & 6 deletions docs/pages/operate.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,17 @@ and reads thread resolution differently.

### GitHub

- **Posting** goes through a GitHub MCP server — tool name set by
`BUBO_GITHUB_MCP_TOOL` (default `create_pull_request_review_comment`). It falls
back to the REST API if the MCP call fails or the tool is missing.
- **Posting** goes through the GitHub REST API — a pull-request review comment
anchored to the diff line. No SCM MCP server or extra CLI is involved.
- **Thread resolution** (`--sync-outcomes`) reads GitHub's GraphQL `reviewThreads`
(`isResolved`), since REST doesn't expose per-thread state. If GraphQL is
unavailable, it falls back to a resolution-blind REST path that still records
posted/deleted/replied transitions.

### GitLab

- **Posting** uses the same MCP-first pattern — the `create_merge_request_thread`
tool, falling back to the REST API (a merge-request discussion) on failure. The
tool name is fixed; there's no `BUBO_GITLAB_MCP_TOOL` override.
- **Posting** goes through the GitLab REST API — a merge-request discussion
anchored to the diff line. No SCM MCP server or extra CLI is involved.
- **Thread resolution** needs no GraphQL — GitLab's REST discussions API already
carries the `resolved` / `resolvable` flags, so `--sync-outcomes` reads accurate
state (including `merged_unresolved`) straight from REST.
Expand Down