Skip to content
Open
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
10 changes: 7 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,14 +206,18 @@ repowise/
3. **Wire up configuration** in these files:
- `rate_limiter.py`, add `RateLimitConfig` to `PROVIDER_DEFAULTS`
- `provider_config.py`, add entry to `PROVIDER_CATALOG`
- `provider_selection.py`, add to `_PROVIDER_DEFAULTS`, `_PROVIDER_ENV`, `_PROVIDER_SIGNUP`, and detection
- `provider_selection.py`, add to `_PROVIDER_DEFAULTS`, `_PROVIDER_ENV`, `_PROVIDER_SIGNUP`, `_PROVIDER_NOTES`, and (for a keyless CLI/local provider) `_LOCAL_PROVIDER_SETUP`, plus detection
- `helpers.py`, add validation in `validate_provider_config()`
- `pricing.py`, add the provider's label prefix to `_COST_TABLE_PREFIX` and to the prefix guard in `_lookup_cost` if it prices at $0.00 — otherwise the `/` in a routed label gets stripped to a bare model name before the table ever sees it
- `generation/cost_tracker.py`, add the label prefix to the passthrough tuple in `is_local_model()` so the cost ledger and the savings estimate agree with `pricing.py`

4. **Update the web UI**: add to `PROVIDERS`, `MODEL_PLACEHOLDERS`, and `PROVIDER_ENV_VARS` in `provider-section.tsx` and `run-config-form.tsx`
4. **Update the web UI**: add to `PROVIDERS`, `MODEL_PLACEHOLDERS`, and `PROVIDER_ENV_VARS` in `packages/web/src/components/settings/provider-section.tsx`, and to `KEYLESS_PROVIDERS` in `packages/ui/src/settings/provider-settings.tsx` if it authenticates without an API key

5. **Add tests** in `tests/unit/test_providers/`: mock the subprocess, test success/error/timeout paths (see `test_codex_cli_provider.py` for the pattern)

6. **Write docs**: `docs/<NAME>.md` and `website/<name>.md`, following `docs/agent/CODEX.md` and `docs/agent/OPENCODE.md`.
6. **Update the drift-gate tests**, which fail on purpose until a new provider gets a deliberate decision instead of an accidental default: `tests/providers/test_registry.py` (asserts the exact builtin provider count) and `tests/unit/server/mcp/test_answer_synthesis_timeout.py` (asserts every provider's `interactive_timeout_s` budget by name)

7. **Write docs**: `docs/agent/<NAME>.md` and `website/<name>.md`, following `docs/agent/CODEX.md` and `docs/agent/OPENCODE.md`.

Adding a new language has a dedicated recipe, see
[docs/architecture/language-support.md](../docs/architecture/language-support.md).
Expand Down
197 changes: 197 additions & 0 deletions docs/agent/OMP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Oh My Pi as an LLM Provider

The `omp` **LLM provider** runs Repowise page generation through a local
**Oh My Pi** install, driven headlessly in one-shot print mode
(`omp -p --mode json`) instead of typed at a prompt. Oh My Pi already holds
credentials for whatever it is configured against — an OAuth subscription, a
provider account, or a plain API key living entirely in its own config — so
Repowise needs none of its own: no `REPOWISE_*` API key, no provider key,
nothing for `omp` to write to `.repowise/.env`.

## `omp` Provider

Use `omp` when you want page generation to run through your local Oh My Pi
install instead of any provider key:

```bash
repowise init --provider omp --yes
```

Or for an existing index:

```bash
REPOWISE_PROVIDER=omp repowise update
```

To persist the choice, put it in `.repowise/config.yaml`:

```yaml
provider: omp
model: omp/default
```

### Prerequisites

```bash
# Install Oh My Pi, then sign in once:
# https://github.com/can1357/oh-my-pi
omp
```

Running `omp` on its own walks you through sign-in, the same first-run step as
`claude login` or a bare `opencode`. Authentication happens out of band
exactly as it does for `claude_cli`, `codex_cli` and `opencode` — the CLI
holds the credentials, and Repowise only ever shells out to it.

### Model selection

`omp/default` is the default, and it is the zero-config path: no `--model`
flag is passed at all, and Oh My Pi's own configuration picks. A specific
model is an Oh My Pi *selector*, not a name Repowise invents:

```bash
repowise init --provider omp --model omp/anthropic/claude-sonnet-4-5
```

Roles work too:

```bash
repowise init --provider omp --model omp/@slow
```

The `omp/` prefix is optional on input — a bare selector round-trips through
`.repowise/config.yaml` the same way. List what your local install can
actually reach:

```bash
omp models --json
```

Repowise's interactive `repowise init` model picker is populated from this
same catalog, so what it offers always matches what Oh My Pi would actually
accept.

### What the provider runs

```bash
omp -p --mode json --no-session --no-extensions --no-skills --no-rules --no-tools \
--config <scratch>/omp-config.yml --system-prompt <scratch>/system-prompt.md
```

Repowise sends the page prompt on stdin — print mode (`-p`) reads non-TTY
stdin as the initial message, so there is no argv length limit to work
around — then parses the JSONL event stream Oh My Pi writes to stdout. The
only event that matters is the assistant `message_end`: it carries the
finished answer text, the token usage, the stop reason, and the model Oh My
Pi actually routed to. A non-zero exit is a failure, with Oh My Pi's own
reason on stderr.

Oh My Pi also exposes a richer bidirectional JSON-RPC transport
([`docs/rpc.md`](https://github.com/can1357/oh-my-pi/blob/main/docs/rpc.md),
`--mode rpc`) with request/response correlation, mid-turn steering, and
host-owned tools — worth knowing about, but none of it applies to a
one-shot completion. Print mode answers one prompt and exits, which keeps
`omp` the same shape as `claude_cli`, `codex_cli` and `opencode`.

### Isolation

Each call runs in a fresh temporary scratch directory, resolved with
`Path.resolve()` and removed when the call finishes, so:

- No `AGENTS.md`-style project context, skills, rules or extensions are
discovered — everything the generator needs is already in the prompt it
sends.
- Built-in tools are off (`--no-tools`), so the call is a pure completion with
no filesystem or network access of its own.
- `--no-session` keeps every generated page out of your Oh My Pi session
history. A 68-page wiki run would otherwise leave 68 resumable sessions
behind.
- A generated config overlay, passed with `--config`, overrides four global
Oh My Pi settings a documentation run has no business inheriting:
- `memory` and `autolearn` off — they add write-capable tools (`learn`,
`manage_skill`), so a docs run never writes to your memory store or
creates a skill on your behalf.
- `advisor` off — otherwise every finished page triggers a second model call
to review it, silently doubling the spend on a 68-page run.
- `tools.approvalMode: always-ask` — MCP servers from your own config still
load (see [Known considerations](#known-considerations)), and a global
`yolo` would let the model run a mutating MCP tool against your machine
with no prompt. The enum is a permissiveness ladder rather than a gate
selector — `write` *auto-approves* writes — so `always-ask` is the only
value that gates anything. A headless run has no UI to answer the prompt,
which is exactly what makes it safe: Oh My Pi fails the call with `Tool
"x" requires approval but no interactive UI available`, the model gets a
tool error, and the turn still finishes with prose.

### Reasoning

Oh My Pi accepts every reasoning level Repowise names, which makes this the
fullest reasoning coverage of any CLI-backed provider:

| Repowise `--reasoning` | Oh My Pi flag |
|---|---|
| `auto` | no `--thinking` flag — your configured thinking level is left alone |
| `off`, `none` | `--thinking off` |
| `minimal`, `low`, `medium`, `high`, `xhigh`, `max` | `--thinking <level>`, passed through unchanged |

### Cost

`omp/*` is priced at **$0.00** in Repowise's cost estimates and cost history,
because Oh My Pi bills against its own account rather than a Repowise API key
— the same reasoning that prices `claude_cli/*`, `codex_cli/*` and
`opencode/*` at zero. The cost Oh My Pi itself reports for the call is still
recorded in the usage record for auditing.

### Concurrency

Each page is a full Oh My Pi process against your own account, so the
provider bounds itself to **4** concurrent processes by default:

```bash
REPOWISE_OMP_CONCURRENCY=2 repowise generate --unwritten
```

The variable is a true override, not a clamp — it raises the limit as well as
lowers it, the same contract as `REPOWISE_CLAUDE_CLI_CONCURRENCY` and its
CLI-provider siblings.

Smoke check:

```bash
omp -p "Return exactly OK"
```

## Known considerations

MCP servers configured in your own Oh My Pi config still load for every call,
and their tool schemas still ride along on the request whether or not the
agent ever calls them — Oh My Pi has no switch to suppress them for a
headless run. They are prompt-cached, so in practice the cost is roughly one
cache write per run rather than per page, but a very large MCP surface will
still make each call slower and spend more of your account's budget than it
strictly needs to. If that matters to you, disable the MCP servers you don't
need in your Oh My Pi config; this provider does not do it on your behalf.

## Security

- Uses `asyncio.create_subprocess_exec` — never `shell=True`.
- Model names are validated against `^@?[a-zA-Z0-9][a-zA-Z0-9._/\-]*$` before
they reach argv. The leading `@` is the only difference from the other
CLI-backed providers' pattern, because Oh My Pi roles are spelled `@slow`.
- Every built-in tool, extension, skill and rule is disabled for the turn
(`--no-tools --no-extensions --no-skills --no-rules`), and the scratch
directory is resolved with `Path.resolve()` before it reaches argv.

## Comparison with Codex CLI and OpenCode

| Aspect | `omp` | `codex_cli` | `opencode` |
|--------|-------|-------------|------------|
| CLI command | `omp -p --mode json` | `codex exec` | `opencode run` |
| Auth | Oh My Pi's own login | `codex login` | OpenCode providers |
| Output format | JSONL via `-p --mode json` | JSONL via `--json` | JSONL via `--format json` |
| Reasoning modes | Full `--thinking` passthrough | `model_reasoning_effort` mapping | Not passed (OpenCode manages it) |

## Official Oh My Pi links

- [Oh My Pi](https://github.com/can1357/oh-my-pi)
- [RPC protocol reference](https://github.com/can1357/oh-my-pi/blob/main/docs/rpc.md)
2 changes: 1 addition & 1 deletion docs/architecture/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ Two things worth knowing before you start:
Full configuration with defaults (`.repowise/config.yaml`):

```yaml
provider: anthropic # anthropic | openai | openrouter | gemini | deepseek | kimi | ollama | litellm | codex_cli | opencode | edenai | mock
provider: anthropic # anthropic | openai | openrouter | gemini | deepseek | kimi | ollama | litellm | codex_cli | claude_cli | opencode | omp | edenai | mock
model: claude-sonnet-4-5 # passed through to the provider
embedding_provider: anthropic
embedding_model: voyage-3
Expand Down
3 changes: 2 additions & 1 deletion docs/reference/CLI_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ All three reach the indexing knobs; the LLM-only knobs appear only when model-wr

| Flag | Description |
|------|-------------|
| `--provider` | LLM provider: `anthropic`, `openai`, `openrouter`, `gemini`, `deepseek`, `kimi`, `ollama`, `litellm`, `codex_cli`, `opencode`, `edenai`, `mock` |
| `--provider` | LLM provider: `anthropic`, `openai`, `openrouter`, `gemini`, `deepseek`, `kimi`, `ollama`, `litellm`, `codex_cli`, `claude_cli`, `opencode`, `omp`, `edenai`, `mock` |
| `--model` | Model name override (e.g., `claude-sonnet-4-6`) |
| `--embedder` | Embedder for semantic search: `gemini`, `openai`, `openrouter`, `ollama`, `edenai`, `mock` (default: auto-detect) |
| `--prose` / `--no-prose` | The single knob over LLM spend. The file, symbol, cycle (SCC), API and infra pages are rendered from structure either way, with no key and no cost. The model-written set is the subsystem (concept) tree plus the repo overview, the architecture diagram, and the onboarding collection: `--prose` writes those as model prose and needs a key; `--no-prose` leaves them as structural stubs, so the whole wiki is keyless and free. Default: prose when a key is available. Full-text search works either way; semantic search needs an embedder. Fill or refill that prose later with [`repowise generate`](#repowise-generate-path). |
Expand Down Expand Up @@ -190,6 +190,7 @@ repowise init # interactive
repowise init --provider anthropic --yes # automated
repowise init --provider codex_cli --codex --yes # use authenticated Codex CLI
repowise init --provider opencode --yes # use local OpenCode CLI
repowise init --provider omp --yes # use a local Oh My Pi install
repowise init --no-prose # free, wiki rendered from structure
repowise init --dry-run # preview cost
repowise init --test-run # quick test (10 files)
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ You can edit this file directly. Changes take effect on the next `init`,

| Key | Default | Meaning |
|-----|---------|---------|
| `provider` | auto-detected | `anthropic`, `openai`, `gemini`, `openrouter`, `deepseek`, `kimi`, `ollama`, `litellm`, `opencode` |
| `provider` | auto-detected | `anthropic`, `openai`, `gemini`, `openrouter`, `deepseek`, `kimi`, `ollama`, `litellm`, `codex_cli`, `claude_cli`, `opencode`, `omp`, `edenai`, `mock` |
| `model` | provider default | Model identifier passed to the provider |
| `embedder` | `mock` | `openai`, `gemini`, `ollama`, `openrouter`, `edenai`, `mock` |
| `embedding_model` | provider default | Embedding model identifier |
Expand Down Expand Up @@ -867,7 +867,7 @@ The `.repowise/.env` file is gitignored automatically.
| `REPOWISE_MODEL` | Override model |
| `REPOWISE_DOC_MODEL` | Override the model used for `get_answer` synthesis specifically |
| `REPOWISE_REASONING` | Override `reasoning` (see valid values above) |
| `REPOWISE_ANSWER_TIMEOUT_S` | Seconds `get_answer` waits for synthesis before giving up. Defaults to a per-provider budget: 60s for the remote API providers, 120s for `ollama` and `litellm`, 180s for `codex_cli` and `opencode`. Raise it if your model is slower than its class suggests, lower it if you would rather an agent fail fast than block. Capped at 600s. Note your MCP client enforces its own tool timeout underneath this one, so setting a value above it produces a client-side error instead of repowise's diagnosable "synthesis exceeded its budget" response |
| `REPOWISE_ANSWER_TIMEOUT_S` | Seconds `get_answer` waits for synthesis before giving up. Defaults to a per-provider budget: 60s for the remote API providers, 120s for `ollama` and `litellm`, 180s for `codex_cli`, `claude_cli`, `opencode`, and `omp`. Raise it if your model is slower than its class suggests, lower it if you would rather an agent fail fast than block. Capped at 600s. Note your MCP client enforces its own tool timeout underneath this one, so setting a value above it produces a client-side error instead of repowise's diagnosable "synthesis exceeded its budget" response |

### Embeddings

Expand Down
4 changes: 3 additions & 1 deletion docs/start/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,12 @@ Prefer to write the whole wiki as part of a fresh index instead? `repowise init`
on its own is interactive: it asks which provider to use, shows a cost estimate,
and waits for you to confirm before spending anything.

Three ways to avoid paying a provider at all:
Four ways to avoid paying a provider at all:

- **Codex subscription:** `repowise init --provider codex_cli` uses your existing
Codex CLI login, no API key. Run `codex login` first.
- **Oh My Pi:** `repowise init --provider omp` uses your existing Oh My Pi
login, no API key. Run `omp` once to sign in first.
- **Fully local:** point it at Ollama with a local embedding model for zero
external calls. See [Config](../reference/CONFIG.md).
- **Stay in index-only mode.** The graph, git, health, risk and dead-code layers
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/repowise/cli/commands/init_cmd/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ def _run_generation_phase(
console.print(f" Languages: {', '.join(lang_parts)}")

# Warn when a local provider runs with default concurrency
local_providers = ("ollama", "codex_cli", "claude_cli", "opencode")
local_providers = ("ollama", "codex_cli", "claude_cli", "opencode", "omp")
if provider.provider_name in local_providers and concurrency > 4:
console.print(
f" [{WARN}]Warning:[/] {provider.provider_name} is a local provider "
Expand Down Expand Up @@ -492,7 +492,7 @@ def _interactive_gate(
default=None,
help=(
"LLM provider name (anthropic, openai, openrouter, gemini, "
"deepseek, kimi, ollama, litellm, codex_cli, claude_cli, opencode, "
"deepseek, kimi, ollama, litellm, codex_cli, claude_cli, opencode, omp, "
"edenai, mock). "
"In a terminal, a missing key is prompted for; openai also asks for "
"an optional OpenAI-compatible Base URL."
Expand Down
18 changes: 16 additions & 2 deletions packages/cli/src/repowise/cli/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,8 +1029,9 @@ def _build(name: str) -> Any:
"KIMI_API_KEY / EDENAI_API_KEY / LITELLM_API_KEY. Use "
"REPOWISE_PROVIDER=claude_cli to use an "
"authenticated Claude Code subscription, REPOWISE_PROVIDER=codex_cli to use "
"an authenticated Codex CLI subscription, or REPOWISE_PROVIDER=opencode "
"to use opencode.",
"an authenticated Codex CLI subscription, REPOWISE_PROVIDER=opencode "
"to use opencode, or REPOWISE_PROVIDER=omp to use a local Oh My Pi "
"install.",
reason="no_provider_configured",
)

Expand Down Expand Up @@ -1218,6 +1219,19 @@ def _is_env_var_exists(var_name: str) -> bool:
)
return warnings

if provider_name == "omp":
import shutil

if not shutil.which("omp"):
warnings.append(
"Provider 'omp' requires the Oh My Pi CLI.\n"
" Install: https://github.com/can1357/oh-my-pi\n"
" Setup: run 'omp' once to sign in\n"
" Models: omp models (list available models)\n"
" Usage: repowise init --provider omp --model omp/anthropic/claude-sonnet-4-5"
)
return warnings

# Validate specific provider
if provider_name not in provider_env_vars:
warnings.append(f"Unknown provider '{provider_name}' - cannot validate configuration")
Expand Down
Loading
Loading