Skip to content

Commit 03a5d6d

Browse files
dmikushinclaude
andcommitted
feat: add free-code provider to repowise provider selection menu
Add free-code (local Claude proxy via claude serve) to: - PROVIDER_CATALOG in provider_config.py — no API key required, base_url defaults to http://localhost:3180 (FREE_CODE_BASE_URL overrides) - _PROVIDER_ENV / _PROVIDER_DEFAULTS in ui.py — shows in the interactive provider selection table with status '✓ no key needed' - _NO_KEY_PROVIDERS set — skips API key prompt, shows a reminder to start 'claude serve' instead Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent f30835b commit 03a5d6d

2 files changed

Lines changed: 45 additions & 5 deletions

File tree

packages/cli/src/repowise/cli/ui.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,16 @@ def print_phase_header(
8282
"anthropic": "claude-sonnet-4-6",
8383
"ollama": "llama3.2",
8484
"litellm": "groq/llama-3.1-70b-versatile",
85+
"free-code": "claude-sonnet-4-6",
8586
}
8687

8788
_PROVIDER_ENV: dict[str, str] = {
8889
"gemini": "GEMINI_API_KEY",
8990
"openai": "OPENAI_API_KEY",
9091
"anthropic": "ANTHROPIC_API_KEY",
9192
"ollama": "OLLAMA_BASE_URL",
93+
# free-code uses FREE_CODE_BASE_URL (optional — defaults to http://localhost:3180)
94+
"free-code": "FREE_CODE_BASE_URL",
9295
}
9396

9497
_PROVIDER_SIGNUP: dict[str, str] = {
@@ -98,6 +101,9 @@ def print_phase_header(
98101
"ollama": "https://ollama.com/download",
99102
}
100103

104+
# Providers that require no API key — always considered available
105+
_NO_KEY_PROVIDERS: set[str] = {"free-code"}
106+
101107

102108
# ---------------------------------------------------------------------------
103109
# .env persistence — save/load API keys in .repowise/.env
@@ -220,10 +226,13 @@ def interactive_mode_select(console: Console) -> str:
220226

221227

222228
def _detect_provider_status() -> dict[str, str]:
223-
"""Return {provider: env_var_name} for providers whose key is set."""
229+
"""Return {provider: env_var_name} for providers whose key is set or that need no key."""
224230
status: dict[str, str] = {}
225231
for prov, env_var in _PROVIDER_ENV.items():
226-
if prov == "gemini":
232+
if prov in _NO_KEY_PROVIDERS:
233+
# No-key providers are always considered available
234+
status[prov] = env_var
235+
elif prov == "gemini":
227236
if os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY"):
228237
status[prov] = env_var
229238
elif os.environ.get(env_var):
@@ -258,12 +267,19 @@ def interactive_provider_select(
258267
table.add_column("Default Model", style="dim")
259268

260269
for idx, prov in enumerate(providers, 1):
261-
status_text = f"[{OK}]✓ API key set[/]" if prov in detected else "[dim]✗ no key[/dim]"
270+
if prov in _NO_KEY_PROVIDERS:
271+
status_text = f"[{OK}]✓ no key needed[/]"
272+
elif prov in detected:
273+
status_text = f"[{OK}]✓ API key set[/]"
274+
else:
275+
status_text = "[dim]✗ no key[/dim]"
262276
default_model = _PROVIDER_DEFAULTS.get(prov, "")
263277
# Mark gemini as recommended
264278
label = prov
265279
if prov == "gemini":
266280
label = f"{prov} [dim](recommended)[/dim]"
281+
elif prov == "free-code":
282+
label = f"{prov} [dim](local Claude proxy)[/dim]"
267283
table.add_row(f"[{idx}]", label, status_text, default_model)
268284

269285
console.print()
@@ -288,7 +304,7 @@ def interactive_provider_select(
288304
chosen = providers[int(chosen_idx) - 1]
289305

290306
# --- inline API key entry if missing ---
291-
if chosen not in detected:
307+
if chosen not in detected and chosen not in _NO_KEY_PROVIDERS:
292308
env_var = _PROVIDER_ENV[chosen]
293309
signup_url = _PROVIDER_SIGNUP.get(chosen, "")
294310
console.print()
@@ -300,6 +316,14 @@ def interactive_provider_select(
300316
if not key:
301317
console.print(f" [{WARN}]Skipped. Please select another provider.[/]")
302318
return interactive_provider_select(console, model_flag, repo_path=repo_path)
319+
elif chosen == "free-code":
320+
base_url = os.environ.get("FREE_CODE_BASE_URL", "http://localhost:3180")
321+
console.print()
322+
console.print(f" [dim]free-code serve endpoint: [cyan]{base_url}[/cyan][/dim]")
323+
console.print(
324+
" [dim]Make sure [bold]claude serve[/bold] is running before starting analysis.[/dim]"
325+
)
326+
console.print()
303327

304328
# --- model ---
305329
default_model = _PROVIDER_DEFAULTS.get(chosen, "")

packages/server/src/repowise/server/provider_config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@
5555
"env_keys": [],
5656
"requires_key": False,
5757
},
58+
{
59+
"id": "free-code",
60+
"name": "free-code (local Claude proxy)",
61+
"default_model": "claude-sonnet-4-6",
62+
"models": ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
63+
"env_keys": ["FREE_CODE_BASE_URL"],
64+
"requires_key": False,
65+
"base_url_env": "FREE_CODE_BASE_URL",
66+
"base_url_default": "http://localhost:3180",
67+
},
5868
{
5969
"id": "litellm",
6070
"name": "LiteLLM",
@@ -203,6 +213,12 @@ def get_chat_provider_instance():
203213

204214
kwargs: dict[str, Any] = {"model": model or catalog["default_model"]}
205215
if api_key:
206-
kwargs["api_key"] = api_key
216+
# For free-code the env var holds the base URL, not an API key
217+
if catalog.get("base_url_env") and catalog["base_url_env"] in (catalog.get("env_keys") or []):
218+
kwargs["base_url"] = api_key
219+
else:
220+
kwargs["api_key"] = api_key
221+
elif catalog.get("base_url_default"):
222+
kwargs["base_url"] = catalog["base_url_default"]
207223

208224
return get_provider(provider_id, with_rate_limiter=False, **kwargs)

0 commit comments

Comments
 (0)