From 345cb7678225029229a1b0251060667d4788da73 Mon Sep 17 00:00:00 2001 From: mountainowl Date: Mon, 6 Jul 2026 20:30:03 -0700 Subject: [PATCH] docs: reconcile docs site with REST-only SCM + standardized LLM config Update the Nextra docs to match the shipped operator surface (PR #134): - SCM is REST-only: drop glab/gh from prerequisites and recipes; the token is now the credential for the tokenized HTTPS clone (per-call auth header, never written to .git/config). Remove denied_tools_regex. - Posting goes through the REST API for both providers; drop the MCP-first posting description and BUBO_GITHUB_MCP_TOOL / create_merge_request_thread. - LLM config: document llm_model (now drives the model via bubo init), llm_model_effort (low/medium/high), llm_base_url (OpenAI-compatible endpoint), and the login-based auth model. Mark llm_api_key_env deprecated and stop emitting it in generated configs; the self-hosted recipe now uses llm_base_url, which bubo init templates into the Codex profile. - Add the [analytics] section (opt-out; BUBO_ANALYTICS / DO_NOT_TRACK). --- docs/components/quickstartShared.tsx | 35 ++++++++---------- docs/components/recipes/claude.ts | 2 +- docs/components/recipes/codex.ts | 4 +- docs/components/recipes/compose.ts | 2 +- docs/components/recipes/github.ts | 7 +--- docs/components/recipes/gitlab.ts | 7 +--- docs/components/recipes/linux.ts | 5 +-- docs/components/recipes/mac.ts | 4 +- docs/components/recipes/selfhosted.ts | 34 ++++++++--------- docs/pages/configuration.mdx | 53 +++++++++++++++++---------- docs/pages/operate.mdx | 10 ++--- 11 files changed, 81 insertions(+), 82 deletions(-) diff --git a/docs/components/quickstartShared.tsx b/docs/components/quickstartShared.tsx index 299ee11..b885814 100644 --- a/docs/components/quickstartShared.tsx +++ b/docs/components/quickstartShared.tsx @@ -136,18 +136,16 @@ export const AGENT = { } as const export const OS_PREREQS: Record = { - 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 = { @@ -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" @@ -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}" @@ -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 @@ -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" @@ -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 @@ -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}" diff --git a/docs/components/recipes/claude.ts b/docs/components/recipes/claude.ts index 7912e66..5cf61bd 100644 --- a/docs/components/recipes/claude.ts +++ b/docs/components/recipes/claude.ts @@ -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`, } diff --git a/docs/components/recipes/codex.ts b/docs/components/recipes/codex.ts index f24ee76..025832b 100644 --- a/docs/components/recipes/codex.ts +++ b/docs/components/recipes/codex.ts @@ -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`, } diff --git a/docs/components/recipes/compose.ts b/docs/components/recipes/compose.ts index cd12877..ee84928 100644 --- a/docs/components/recipes/compose.ts +++ b/docs/components/recipes/compose.ts @@ -47,7 +47,7 @@ enabled = true` const gatewayLine = agent === 'selfhosted' - ? '\n# The OpenAI-compatible endpoint URL.\ngateway_base_url = ""' + ? '\n# The OpenAI-compatible endpoint URL.\nllm_base_url = ""' : '' const srcKind = install === 'docker' ? 'registry' : 'index' const sourceValue = install === 'docker' ? 'ghcr.io/mountainowl/bubo' : 'https://pypi.org/project/bubo/' diff --git a/docs/components/recipes/github.ts b/docs/components/recipes/github.ts index 47fbd92..f154e1a 100644 --- a/docs/components/recipes/github.ts +++ b/docs/components/recipes/github.ts @@ -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] diff --git a/docs/components/recipes/gitlab.ts b/docs/components/recipes/gitlab.ts index 75cbe1c..7c6222b 100644 --- a/docs/components/recipes/gitlab.ts +++ b/docs/components/recipes/gitlab.ts @@ -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] diff --git a/docs/components/recipes/linux.ts b/docs/components/recipes/linux.ts index f8b72ac..8910a5d 100644 --- a/docs/components/recipes/linux.ts +++ b/docs/components/recipes/linux.ts @@ -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}"`, } diff --git a/docs/components/recipes/mac.ts b/docs/components/recipes/mac.ts index 9399c43..d50ef54 100644 --- a/docs/components/recipes/mac.ts +++ b/docs/components/recipes/mac.ts @@ -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}"`, } diff --git a/docs/components/recipes/selfhosted.ts b/docs/components/recipes/selfhosted.ts index 535a21a..8a885d4 100644 --- a/docs/components/recipes/selfhosted.ts +++ b/docs/components/recipes/selfhosted.ts @@ -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', @@ -18,22 +19,17 @@ export const selfhosted: AgentFragment = { cli: AGENT.codex.cli, cliName: 'Codex', configBlock: `[agents] -llm_model = "" # 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 = "" # 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 = "" -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"`, }, } diff --git a/docs/pages/configuration.mdx b/docs/pages/configuration.mdx index a6a0497..55cc657 100644 --- a/docs/pages/configuration.mdx +++ b/docs/pages/configuration.mdx @@ -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]` @@ -54,10 +52,9 @@ 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 | `"/api/v4"` | GitLab REST API URL used by GitLab tools. | +| `api_url` | string | `"/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]` @@ -65,9 +62,9 @@ Used when `[scm].provider = "github"`. | Key | Type | Default | Description | |---|---|---|---| -| `api_url` | string | `"https://api.github.com"` | GitHub REST API base. Use `https:///api/v3` for GitHub Enterprise Server. | +| `api_url` | string | `"https://api.github.com"` | GitHub REST API base. Use `https:///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]` @@ -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. | @@ -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. @@ -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" @@ -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" @@ -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" diff --git a/docs/pages/operate.mdx b/docs/pages/operate.mdx index 0f43693..db457e7 100644 --- a/docs/pages/operate.mdx +++ b/docs/pages/operate.mdx @@ -127,9 +127,8 @@ 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 @@ -137,9 +136,8 @@ and reads thread resolution differently. ### 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.