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
74 changes: 65 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,72 @@ All notable changes to llm-relay are documented here.

## [Unreleased]

## [0.9.6] - 2026-05-21

> Single-command Windows install + atomic `llm-relay init`. Surfaced by
> 2026-05-21 hmj PC dogfood: the previous `init` would write
> `ANTHROPIC_BASE_URL` into `~/.claude/settings.json` before the proxy
> server was actually serving traffic, so any running Claude Code session
> would route to a dead port and the symptom looked like a generic API
> connection error. This release makes that ordering atomic and ships a
> PowerShell one-line installer.

### Added
- **One-command Windows installer** (`scripts/install.ps1`): for users
with Python 3.9+ already installed, the entire install flow collapses
to one PowerShell command:
```powershell
irm https://raw.githubusercontent.com/ArkNill/llm-relay/main/scripts/install.ps1 | iex
```
The script verifies Python, detects an active venv (uses it if
present, else falls back to `--user` with a PATH hint), `pip install`s
`llm-relay[all]`, then calls `llm-relay init` which handles
daemon + health-gate + routing in the right order. Documented in
`README.md` "One-command install (Windows native)".
- **README "Prerequisites" section** spelling out the Python 3.9+
requirement, the recommended venv setup, and platform-specific install
hints (winget / Homebrew / apt).

### Changed
- **`llm-relay init` is now atomic** (`setup_init.run_init`). New ordering:
1. Detect CLIs, find port, init DB, write config, init knowledge dir.
2. **Start the proxy server.**
3. Health-gate (`/_health` polling).
4. Configure Claude Code (`ANTHROPIC_BASE_URL`, MCP) **only after**
the server is verified healthy.
Routing is never activated unless the proxy actually responds, so an
abort at step 2 or step 3 leaves `~/.claude/settings.json` untouched
-- no more "ConnectionRefused on port 8083" surprises in your next
Claude Code session.
- **`--skip-server` now also skips routing**. Previously it was a UX
trap: server skipped, but `settings.json` still gained an
`ANTHROPIC_BASE_URL` pointing at the not-running port. Now
`--skip-server` skips the server, the health gate, AND the
Claude Code routing change as a single decision. The summary message
surfaces this explicitly so a follow-up `llm-relay serve` + re-run is
the documented path to enabling routing.
- **Windows background daemon goes through `win_service.start_daemon`**
(`_start_server` in `setup_init.py`). The previous Popen +
`CREATE_NEW_PROCESS_GROUP` left the proxy tied to its job object on
Windows so it died when the parent SSH session disconnected (observed
during the 2026-05-21 dogfood). The win_service path uses pythonw plus
`CREATE_BREAKAWAY_FROM_JOB`, which detaches cleanly.

### Tests
- New `test_skip_server_does_not_write_settings_json` regression in
`tests/test_api/test_init.py` pins the atomic contract.

### Fixed
- **Docker image build** (`Dockerfile`): removed the unconditional
`COPY vendor/tokpress /tmp/tokpress` + `pip install` step. The vendor
source is kept outside the repository, so the COPY always failed in
GitHub Actions and the Docker workflow has been broken on every tag
since `v0.9.2`. The proxy already imports `tokpress` inside a
`try/except ImportError` guard, so the image runs unchanged when the
package is absent (`_tokpress_available` simply stays `False`). The
v0.9.5 image is rebuilt via `gh workflow run docker.yml -f tag=0.9.5`
after this change lands on `main`.
- **Docker image build** (`Dockerfile`, 0.9.5 carry-over): removed the
unconditional `COPY vendor/tokpress /tmp/tokpress` + `pip install`
step. The vendor source is kept outside the repository, so the COPY
always failed in GitHub Actions and the Docker workflow had been
broken on every tag since v0.9.2. The proxy already imports `tokpress`
inside a `try/except ImportError` guard, so the image runs unchanged
when the package is absent (`_tokpress_available` simply stays
`False`). The v0.9.5 image was rebuilt via
`gh workflow run docker.yml -f tag=0.9.5` after the change landed on
main; the v0.9.6 image rebuilds automatically on the new tag.

## [0.9.5] - 2026-05-21

Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,37 @@ Unified LLM usage management — API proxy, session diagnostics, multi-CLI orche

## Install

### One-command install (Windows native)

For Windows users who just want it running, after Python 3.9+ is installed:

```powershell
irm https://raw.githubusercontent.com/ArkNill/llm-relay/main/scripts/install.ps1 | iex
```

That script `pip install`s `llm-relay[all]`, starts the proxy as a Windows
background daemon, health-gates it, and only then routes Claude Code through
it. Routing is never activated unless the proxy actually responds, so this is
safe to run on a machine where Claude Code is already configured -- the
worst case is the install aborts with a clear message and leaves your
existing setup untouched. See [Prerequisites](#prerequisites) below for the
Python requirement and venv guidance.

If you would rather do it by hand (Linux, macOS, or just to see each step),
keep reading.

### Prerequisites

- **Python 3.9 or newer** (3.12 recommended). We do not bundle a Python
runtime; install it once and llm-relay reuses it.
- Windows: `winget install Python.Python.3.12` or
[python.org/downloads](https://www.python.org/downloads/)
- macOS: `brew install python@3.12`
- Linux: your distribution's package manager (`apt install python3.12`,
`dnf install python3.12`, etc.)
- **(Recommended) A virtual environment.** Clean uninstall, no PATH
surprises, isolated dependency tree.

### 1. Set up Python environment

<details>
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "llm-relay"
version = "0.9.5"
version = "0.9.6"
description = "Unified LLM usage management — API proxy, session diagnostics, multi-CLI orchestration."
readme = "README.md"
license = "MIT"
Expand Down
148 changes: 148 additions & 0 deletions scripts/install.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# llm-relay one-command installer for Windows native.
#
# Usage from PowerShell:
# irm https://raw.githubusercontent.com/ArkNill/llm-relay/main/scripts/install.ps1 | iex
#
# What this does:
# 1. Verifies Python 3.9+ is on PATH.
# 2. Detects an active virtual environment (uses it if present).
# 3. pip install --upgrade "llm-relay[all]".
# 4. Calls `llm-relay init` -- atomic: starts the Windows daemon, waits
# for /_health, then writes ANTHROPIC_BASE_URL into ~/.claude/settings.json.
# Routing is NEVER activated unless the proxy actually responds.
# 5. Prints the dashboard URL.
#
# What this does NOT do:
# - Install Python (we don't bundle a runtime).
# - Install Claude Code / Codex / Gemini (vendor responsibility).
# - Modify your shell profile or PATH (we surface a hint if --user install
# puts the entry point somewhere PATH doesn't see).
#
# Prerequisites and venv guidance: see README.md.

$ErrorActionPreference = 'Stop'

function Write-Step($msg) {
Write-Host ""
Write-Host "==> $msg" -ForegroundColor Cyan
}

function Write-OK($msg) {
Write-Host " [OK] $msg" -ForegroundColor Green
}

function Write-Warn($msg) {
Write-Host " [WARN] $msg" -ForegroundColor Yellow
}

function Write-Err($msg) {
Write-Host " [FAIL] $msg" -ForegroundColor Red
}


# ── 1. Python ──────────────────────────────────────────────────────────────

Write-Step "Checking Python"

$py = Get-Command python -ErrorAction SilentlyContinue
if (-not $py) {
Write-Err "python not found on PATH."
Write-Host ""
Write-Host "Install Python 3.9 or newer first. Quick options:" -ForegroundColor White
Write-Host " winget install Python.Python.3.12" -ForegroundColor Gray
Write-Host " or download from https://www.python.org/downloads/" -ForegroundColor Gray
Write-Host ""
Write-Host "Full prerequisites and venv guidance:" -ForegroundColor White
Write-Host " https://github.com/ArkNill/llm-relay#prerequisites" -ForegroundColor Gray
exit 1
}

$pyVer = (& python --version 2>&1)
Write-OK "$pyVer at $($py.Source)"

# Verify >= 3.9
$verMatch = [regex]::Match($pyVer, 'Python\s+(\d+)\.(\d+)')
if ($verMatch.Success) {
$major = [int]$verMatch.Groups[1].Value
$minor = [int]$verMatch.Groups[2].Value
if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 9)) {
Write-Err "Python $major.$minor is older than the required 3.9."
Write-Host " Upgrade with: winget install Python.Python.3.12" -ForegroundColor Gray
exit 1
}
}


# ── 2. venv detection ──────────────────────────────────────────────────────

Write-Step "Checking for active virtual environment"

$pipUserFlag = @()
if ($env:VIRTUAL_ENV) {
Write-OK "venv active at $env:VIRTUAL_ENV (installing there)"
} else {
Write-Warn "No venv active. Falling back to --user install."
Write-Host " (A venv is recommended for clean uninstall. See README.)" -ForegroundColor Gray
$pipUserFlag = @('--user')
}


# ── 3. pip install ─────────────────────────────────────────────────────────

Write-Step "Installing llm-relay[all] from PyPI"

$pipArgs = @('-m', 'pip', 'install') + $pipUserFlag + @('--upgrade', 'llm-relay[all]')
& python @pipArgs

if ($LASTEXITCODE -ne 0) {
Write-Err "pip install failed (exit code $LASTEXITCODE)."
exit $LASTEXITCODE
}
Write-OK "Installed."


# ── 4. PATH sanity for --user installs ─────────────────────────────────────

if (-not $env:VIRTUAL_ENV) {
$userScripts = & python -c "import sysconfig; print(sysconfig.get_path('scripts', f'{sysconfig.get_default_scheme()}_user'))"
if ($userScripts -and (Test-Path $userScripts)) {
$pathParts = $env:PATH -split ';'
if ($pathParts -notcontains $userScripts) {
Write-Warn "Your --user Scripts dir is not on PATH:"
Write-Host " $userScripts" -ForegroundColor Gray
Write-Host " Add it once via:" -ForegroundColor Gray
Write-Host " [Environment]::SetEnvironmentVariable('Path', `"`$env:Path;$userScripts`", 'User')" -ForegroundColor Gray
Write-Host " then reopen this terminal. Continuing with the absolute path for this run." -ForegroundColor Gray
$env:PATH = "$env:PATH;$userScripts"
}
}
}


# ── 5. llm-relay init ──────────────────────────────────────────────────────

Write-Step "Running llm-relay init (atomic: server + health-gate + routing)"

& llm-relay init
$initExit = $LASTEXITCODE

if ($initExit -ne 0) {
Write-Err "llm-relay init failed (exit code $initExit)."
Write-Host " Server may not be running. Inspect:" -ForegroundColor Gray
Write-Host " Get-Content `$env:USERPROFILE\.llm-relay\service-error.log -Tail 30" -ForegroundColor Gray
exit $initExit
}


# ── 6. Done ────────────────────────────────────────────────────────────────

Write-Step "Install complete"
Write-Host " Dashboard: http://localhost:8083/dashboard/" -ForegroundColor White
Write-Host " Display: http://localhost:8083/display/" -ForegroundColor White
Write-Host ""
Write-Host " Verify everything:" -ForegroundColor Gray
Write-Host " llm-relay verify all" -ForegroundColor Gray
Write-Host ""
Write-Host " Roll back (turn proxy off, leave package installed):" -ForegroundColor Gray
Write-Host " llm-relay service stop" -ForegroundColor Gray
Write-Host " llm-relay service uninstall" -ForegroundColor Gray
2 changes: 1 addition & 1 deletion src/llm_relay/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
Part of the Mirror Agent ecosystem (open-network DLC).
"""

__version__ = "0.9.5"
__version__ = "0.9.6"
2 changes: 1 addition & 1 deletion src/llm_relay/detect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from typing import TYPE_CHECKING

__version__ = "0.9.5"
__version__ = "0.9.6"
__all__ = ["__version__", "get_all_detectors", "get_detectors_for_provider"]

if TYPE_CHECKING:
Expand Down
Loading
Loading