mirror of
https://github.com/dtzp555-max/olp.git
synced 2026-07-22 13:35:10 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
704d4fc8a0 | ||
|
|
6605b7b14a |
@@ -6,6 +6,57 @@ All notable changes to OLP land here. Per `CLAUDE.md` release_kit overlay, this
|
||||
|
||||
(empty — Phase 5 entries land here once Phase 5 opens)
|
||||
|
||||
## v0.4.4 — 2026-05-26
|
||||
|
||||
### D78 — `bin/olp-connect` stale-strings cleanup + README CDN-safe URL + repo-visibility flip
|
||||
|
||||
Patch release on top of v0.4.3. Three small issues caught when running `olp-connect` for real on MacBook (D77 client-install verification):
|
||||
|
||||
- **G11 fix (repo visibility).** Repo `dtzp555-max/olp` flipped from PRIVATE → PUBLIC during this session, closing the original G11 finding (`bash <(curl -fsSL .../main/bin/olp-connect)` returned 404 because anonymous curl can't fetch from private repos). README's `/main/` URL works going forward; GitHub's raw CDN may serve a stale 404 for `/main/` for ~5-15min after the visibility flip due to negative caching. D78 defends against this by adding a **tag-pinned URL (`/v0.4.4/bin/olp-connect`) as the primary recommendation in README**, with `/main/` listed as an alternative for trusted-head users. Tag-pinned URLs bypass the negative-cache because the tag ref was never queried while the repo was private.
|
||||
- **G12 fix (`detect_openclaw` claimed plugin not shipped).** `bin/olp-connect`'s OpenClaw detection block said `"The OpenClaw OLP plugin (D71-D73) is NOT YET SHIPPED"` — but D71-D73 shipped `olp-plugin/` at v0.4.0. D78 replaces the stale text with real install instructions: `git clone` + `openclaw plugins install ./olp-plugin/` (or symlink), edit `~/.openclaw/openclaw.json` with a dedicated bot apiKey, restart gateway. Points at `docs/integrations/openclaw.md` for the full setup.
|
||||
- **G13 fix (`olp-connect` self-version hardcoded literal).** Pre-D78 the script declared `OLP_CONNECT_VERSION="0.4.0-phase4"` as a hardcoded literal that nobody updated through v0.4.1 / v0.4.2 / v0.4.3 (the maintain-the-literal-per-release pattern is reliably forgotten). D78 derives the version at runtime from the sibling `package.json` via python3 — when the script is invoked from a checked-out repo, version resolves to the actual `package.json` value; when invoked via `curl … | bash` with no on-disk package.json next to it, falls back to `unknown`. Now `bash bin/olp-connect --version` prints `olp-connect 0.4.4` automatically with no manual touch needed at the next release.
|
||||
|
||||
**Pre-publish audit.** Per `~/.cc-rules/docs/guides/pre-publish-audit.md` checklist (2026-05-26 session, before the visibility flip):
|
||||
- Identity scrub: 0 hits (no personal names / hostnames / home paths / personal emails leaked into the working tree)
|
||||
- Credential scrub: 0 real tokens — all `olp_` matches are placeholder (`olp_XXXX...`) or test fixtures (`olp_not-a-real-key-...`); gitleaks: "no leaks found"
|
||||
- Git-history author emails: 78 commits, two emails (`dtzp555@gmail.com` local + `taodeng1977@gmail.com` GitHub-account squash-merges). Maintainer chose Option A (accept) — the GitHub-account email was already verified-public on the maintainer's GitHub profile, so the visibility flip exposes nothing new.
|
||||
|
||||
**Test count:** 717 (v0.4.3) → 720 (v0.4.4). +3 D78 regression tests in Suite 36:
|
||||
- 36v — pins absence of `NOT YET SHIPPED` text + presence of real install path
|
||||
- 36w — pins runtime version derivation from package.json (hardcoded literal gone)
|
||||
- 36x — pins README's tag-pinned-URL recommendation
|
||||
|
||||
**Authority:** D77 MacBook client-install verification session (2026-05-26); `~/.cc-rules/docs/guides/pre-publish-audit.md`. Process learning: every README that includes a `curl <raw-URL> | bash` install pattern should pin to a release tag (not `/main/`) for CDN-cache resilience. The /main/ form is correct for the long-tail (when no negative cache exists) but the tag-pinned form survives the visibility-flip transient + survives any future force-push to main.
|
||||
|
||||
**Out of D78 scope:**
|
||||
- F6 (doctor client-side vs server-side check separation) — Phase 5 ADR amendment.
|
||||
- D75 reviewer P2-1 (ADR 0004 per-hop schema amendment) + P2-2 (defensive `typeof hopModel === 'string'` invariant) — both genuine follow-ups, neither blocking.
|
||||
- `scripts/migrate-from-ocp.mjs` — Phase 7.
|
||||
|
||||
## v0.4.3 — 2026-05-26
|
||||
|
||||
### D76 — README install-path overhaul + `OLP_BIND` env + AI-driven install prompt + ADR 0011 amendment
|
||||
|
||||
Patch release closing the install-experience gap. v0.4.0–v0.4.2 README's Quick Start was placeholder text with fictional commands (`npm install -g @dtzp555-max/olp` — package isn't published; `olp setup` / `olp start` — don't exist). 10 real gaps catalogued + fixed in one D-day; `OLP_BIND` env wired so the documented LAN onboarding flow actually works; AI-driven install prompt added per the Phase 4 charter brainstorm's #2 OCP inheritance candidate (was deferred at D64-D67 to the doctor framework only; D76 closes the README half).
|
||||
|
||||
- **G1-G7 (README "Quick Start" was fictional)** — rewrote § "Manual install" with the real sequence: prerequisites (Node ≥ 18 + provider CLI install matrix) → `git clone` → `npm test` verify → `olp-keys keygen --owner` first → provider OAuth (claude/codex/mistral per-CLI flows) → write `~/.olp/config.json` with the minimum that actually serves traffic → `npm start` → smoke-test → IDE pointing. Each step empirically verified against the PI231 + Mac mini E2E session (2026-05-26).
|
||||
- **G8 (LAN unreachable — F5)** — added `OLP_BIND` env (default `127.0.0.1`). Operators set `OLP_BIND=0.0.0.0` (or a specific LAN IP) to accept LAN connections so `olp-connect <ip>` can actually reach the server. Pre-D76 the server was hard-coded to `server.listen(PORT, '127.0.0.1', ...)`, making the documented LAN-onboarding flow only usable through an SSH tunnel. ADR 0011's original wording referenced a `BIND_ADDRESS` concept that didn't exist; D76 makes it operational.
|
||||
- **G10 (no AI-install pattern)** — README § "Install with your AI (the fast path)" added. Verbatim prompt that the operator pastes into Claude Code / Cursor / Copilot / Aider; the AI follows the README + uses `olp doctor --json` machine-readable `next_action.ai_executable[]` (D64-D67) for self-repair, stopping only when `human_required[]` is non-empty (the provider OAuth dances). This closes the Phase 4 brainstorm Top-5 inheritance candidate #2 — the OCP "paste this prompt" pattern that D64-D67 only half-built.
|
||||
- **Opening compressed** — § "Why OLP" (3 paragraphs of OCP billing history) removed from the top. The OCP-trigger context moved to § "Migration from OCP" at the bottom, condensed into a single paragraph. New users land on value-prop + § "What you get" + § "Install with your AI" / § "Manual install" without needing to digest 2026-05-14 / 2026-06-15 Anthropic billing history first. OCP users get a one-line pointer at the top.
|
||||
- **§ "Configuration" full schema documentation** — replaced the placeholder with the actual `~/.olp/config.json` schema including every field that v0.4.x reads. Cross-references ADR 0004/0007/0010/0011.
|
||||
- **§ "Environment Variables" extended** — added `OLP_BIND`, `OLP_API_KEY`, `OLP_OWNER_TOKEN`, `OLP_PROXY_URL` rows that were used throughout the manual-install flow but undocumented.
|
||||
|
||||
**ADR 0011 § "Deployment configurations" amendment.** Codifies the three deployment trust contexts (`127.0.0.1` loopback / RFC1918 + tailnet LAN / `0.0.0.0` public — with `advertise_anonymous_key: true` only safe in the first two). Documents the new `anonymous_key_advertised_with_lan_bind` startup warn event. Closes ADR 0011's pre-D76 dangling reference to a non-existent `BIND_ADDRESS`.
|
||||
|
||||
**Test count:** 714 (v0.4.2) → 717 (v0.4.3). +3 D76 regression tests in Suite 36 (36s/36t/36u) pinning `OLP_BIND` wiring + safety warn + ADR amendment.
|
||||
|
||||
**Out of D76 scope (deferred):**
|
||||
- F6 (doctor client-side vs server-side check separation) — needs design ADR for a `--remote` mode. Phase 5.
|
||||
- D75 reviewer P2-1 (ADR 0004 amendment for per-hop schema) + P2-2 (defensive `typeof hopModel === 'string'`) — both genuine follow-ups, neither blocking.
|
||||
- `scripts/migrate-from-ocp.mjs` — Phase 7.
|
||||
|
||||
**Authority:** PI231 + Mac mini E2E session (2026-05-26, post-v0.4.2 verification revealed the 10 README gaps); ADR 0011 amendment self-cites; Phase 4 charter (ADR 0010) Top-5 inheritance candidate #2 (AI-driven self-repair). Process learning: every D-day reviewer rubric should add "open README in §-Quick-Start and verify the commands literally exist + work in the current repo" — would have caught G1-G7 at v0.4.0.
|
||||
|
||||
## v0.4.2 — 2026-05-26
|
||||
|
||||
### Post-v0.4.1 hotfix batch (D75) — real-machine E2E findings
|
||||
|
||||
@@ -1,55 +1,208 @@
|
||||
# OLP — Open LLM Proxy
|
||||
|
||||
A personal- and family-scale multi-provider LLM proxy. One HTTP endpoint, many subscriptions behind it, automatic routing, automatic fallback, content-addressed caching — so your IDEs and family clients keep working as long as *any* of your subscriptions has quota left.
|
||||
A personal- and family-scale multi-provider LLM proxy. One HTTP endpoint, many subscriptions behind it, automatic routing + fallback + content-addressed caching. Your IDEs and family clients keep working as long as **any** of your subscriptions has quota left.
|
||||
|
||||
> **Status:** v0.4.0 shipped (2026-05-26) — Phase 1 multi-provider proxy core (v0.1.0 + v0.1.1) + Phase 2 multi-key auth + audit + owner gating + keygen CLI (v0.2.0) + Phase 3 Dashboard + audit query layer + daily audit rotation (v0.3.0) + Phase 4 Operator + Client UX (v0.4.0): SSE heartbeat / `olp` Node CLI + `olp doctor` framework / `olp-connect` zero-config LAN setup / `/health.anonymousKey` opt-in / `/olp` Telegram-Discord plugin / 6-IDE integration docs. Phase 5 scope is open — candidates per ADR 0010 § Out-of-Phase-4-scope: `/v1/messages` (gated on ADR 0009 P0 outcome + named family CC user), context-window-exceeded fallback trigger, per-(provider, model) live stats. Sections marked _placeholder_ land alongside the relevant phase of work (see [phase plan](#phase-plan)).
|
||||
> **Status:** v0.4.3 shipped, 714+ tests. Phase 4 (Operator + Client UX) closed; Phase 5 scope is open. Coming from [OCP](https://github.com/dtzp555-max/ocp)? See [§ Migration from OCP](#migration-from-ocp).
|
||||
|
||||
---
|
||||
|
||||
## Why OLP
|
||||
## What you get
|
||||
|
||||
On 2026-05-14, Anthropic announced (effective 2026-06-15) that `claude -p`, the Agent SDK, and third-party agent traffic move out of the Pro/Max subscription pool into a separate fixed monthly Agent SDK Credit pool. [OCP](https://github.com/dtzp555-max/ocp), OLP's predecessor, was a proxy around a single CLI — its core assumption was *"subscription = unlimited within rate limits"*. That assumption breaks for Anthropic on the effective date.
|
||||
|
||||
The structural response is to stop relying on one provider's subscription terms remaining favourable. OLP spreads risk across multiple providers whose subscriptions still include CLI/programmatic use, routes intelligently between them, and caches aggressively so every request that does spawn a CLI counts.
|
||||
|
||||
OLP is **not**: a commercial multi-tenant SaaS; an enterprise gateway competing with LiteLLM / OpenCode / CLIProxyAPI on breadth; a model-capability router ("route to the smartest model" — you pick the model); a conversation-state store (your client handles that).
|
||||
|
||||
See [`ALIGNMENT.md`](./ALIGNMENT.md) for OLP's constitution and [`docs/adr/`](./docs/adr/) for the founding ADRs.
|
||||
- **OpenAI-compatible** `/v1/chat/completions` endpoint — any IDE that speaks OpenAI (Cline / Continue.dev / Cursor / Aider) plugs in
|
||||
- **Multi-provider chain** — primary fails / quota dies → automatically falls back to the next provider (anthropic ↔ codex ↔ mistral by default; risk-tier framework guards which ones get enabled)
|
||||
- **Content-addressed cache** — repeat requests don't re-spawn the CLI; streaming requests dedup via singleflight tee
|
||||
- **Multi-key auth** — owner key with full visibility, family-member keys with per-key audit log + per-provider scoping
|
||||
- **Telegram / Discord** `/olp` slash commands (read-only — for "is OLP up?" checks from anywhere)
|
||||
- **AI-driven self-repair** — `olp doctor --json` emits machine-readable `next_action.ai_executable[]` so a Claude Code / Cursor / Copilot session can fix install issues for you (see [§ Install with your AI](#install-with-your-ai-the-fast-path))
|
||||
- **Observability** — owner-only `/dashboard` (quota / 24h stats / 30d spend trend / top fallback chains)
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## Install with your AI (the fast path)
|
||||
|
||||
_placeholder — lands with Phase 1._
|
||||
If the manual steps feel like a lot, paste this verbatim into your AI coding assistant (Claude Code / Cursor / Copilot / Aider). It walks you through everything:
|
||||
|
||||
Anticipated shape:
|
||||
```
|
||||
I want to install OLP (Open LLM Proxy) on this machine. The repo is at
|
||||
https://github.com/dtzp555-max/olp and ships these tools that you can use:
|
||||
|
||||
```bash
|
||||
# install
|
||||
npm install -g @dtzp555-max/olp
|
||||
1. `git clone` the repo to ~/olp
|
||||
2. README.md § "Manual install" has the sequenced steps. Follow them in order.
|
||||
3. After EACH step, run `node ~/olp/bin/olp.mjs doctor --json` (NOT `npx olp`
|
||||
— the package isn't on npm). Parse the JSON. The `kind` field tells you
|
||||
the state: noop / fresh_install / fix_server / fix_oauth / fix_provider /
|
||||
fix_config.
|
||||
4. If `kind != noop`, run the commands listed in `next_action.ai_executable[]`
|
||||
verbatim. Then re-run doctor to verify.
|
||||
5. STOP and ask me only when `next_action.human_required[]` is non-empty.
|
||||
That's where I need to do a browser OAuth flow you can't do for me.
|
||||
|
||||
# run setup (writes ~/.olp/config.json, asks which providers to enable)
|
||||
olp setup
|
||||
The provider CLIs OLP spawns (claude / codex / vibe) need their own one-time
|
||||
OAuth — those are the only steps I personally have to do (Claude.ai login,
|
||||
ChatGPT login, Mistral API key). Everything else (clone, npm install of the
|
||||
provider CLIs, owner-key generation, config.json bootstrap, server start) is
|
||||
in your `ai_executable[]` and you should run it without asking.
|
||||
|
||||
# start the proxy (default port 4567 since v0.4.0 — moved off OCP's 3456 so
|
||||
# OLP and OCP can co-host on the same machine. Set OLP_PORT=3456 if you have
|
||||
# no OCP on the machine and want the old default.)
|
||||
olp start
|
||||
|
||||
# point your IDE at http://localhost:4567/v1/chat/completions with the OLP API key from `olp keys list`.
|
||||
Begin.
|
||||
```
|
||||
|
||||
**Family-on-LAN onboarding (D68-D70).** For other devices on the same network, run on the client device:
|
||||
Then sit back and respond when it asks for OAuth confirmation. This pattern works because `olp doctor` is purpose-built for AI consumption — every failure mode has a shell-executable repair command AND a human-required step listed separately.
|
||||
|
||||
---
|
||||
|
||||
## Manual install (5-10 min)
|
||||
|
||||
### 0. Prerequisites
|
||||
|
||||
- **Node.js ≥ 18.** Verify: `node --version`
|
||||
- **The provider CLIs you want OLP to spawn.** Install whichever you'll actually use:
|
||||
|
||||
| Provider | Install | Subscription |
|
||||
|---|---|---|
|
||||
| `anthropic` (`claude -p`) | `npm install -g @anthropic-ai/claude-code` | Claude Pro/Max (OAuth) |
|
||||
| `openai` (`codex exec`) | `npm install -g @openai/codex` | ChatGPT Plus/Pro (OAuth) or OpenAI API key |
|
||||
| `mistral` (`vibe --prompt`) | follow the `vibe` install docs | Le Chat Pro API key |
|
||||
|
||||
You only need to install the ones you'll route to. Single-provider OLP works fine.
|
||||
|
||||
### 1. Clone and verify the test suite
|
||||
|
||||
```bash
|
||||
# Detects Cline / Continue.dev / Cursor / Aider / OpenClaw installed locally
|
||||
# and writes per-tool config pointing at the OLP host. Requires `python3`.
|
||||
olp-connect <olp-host-ip>
|
||||
git clone https://github.com/dtzp555-max/olp.git ~/olp
|
||||
cd ~/olp
|
||||
npm test # 714+ tests, ~5s, no external deps
|
||||
```
|
||||
|
||||
If the OLP host has `auth.advertise_anonymous_key: true` AND a key was created with `olp-keys keygen --anonymous --advertise`, `olp-connect` picks up the token from `/health.anonymousKey` — zero out-of-band token paste required. See [ADR 0011](./docs/adr/0011-anonymous-key-deployment-context.md) for the trusted-LAN-only invariant.
|
||||
(If `npm test` fails here, stop — that means your Node version or the repo state is broken. Don't proceed to step 2.)
|
||||
|
||||
Per-IDE setup details: [`docs/integrations/`](./docs/integrations/README.md).
|
||||
### 2. Bootstrap the owner key
|
||||
|
||||
The owner key is what you (and `olp-connect`) use to authenticate to OLP. Default config has `auth.allow_anonymous: false`, so you need a key BEFORE the server starts accepting requests.
|
||||
|
||||
```bash
|
||||
node ~/olp/bin/olp-keys.mjs keygen --owner --name=$(whoami)-laptop
|
||||
# Prints the plaintext token ONCE. Copy it now — you can't recover it later.
|
||||
# Example: olp_l23-PN46tDljmPATV94-KfOgOBO0Ed8theVjTdAgQoY
|
||||
```
|
||||
|
||||
Export it so the CLI subcommands can use it:
|
||||
|
||||
```bash
|
||||
export OLP_API_KEY=olp_l23-PN46... # paste your real token
|
||||
```
|
||||
|
||||
(Add to `~/.bashrc` / `~/.zshrc` to persist.)
|
||||
|
||||
### 3. Authenticate the providers (one-time OAuth)
|
||||
|
||||
Run each provider's own login flow. OLP's anthropic / openai / mistral plugins spawn these CLIs and reuse their cached credentials — OLP itself never touches the OAuth dance.
|
||||
|
||||
```bash
|
||||
# Anthropic (Claude Pro/Max subscription)
|
||||
claude setup-token
|
||||
# Opens a TUI / prints a URL. Authorize in browser. Paste the returned code.
|
||||
# Result: ~/.claude/.credentials.json
|
||||
|
||||
# OpenAI (ChatGPT subscription)
|
||||
codex login --device-auth
|
||||
# Prints a https://auth.openai.com/codex/device URL + 10-char code.
|
||||
# Open URL in browser, enter code, authorize.
|
||||
# Result: ~/.codex/auth.json
|
||||
|
||||
# Mistral (Le Chat API key)
|
||||
export MISTRAL_API_KEY=sk-... # add to ~/.bashrc to persist
|
||||
```
|
||||
|
||||
### 4. Write a minimum config
|
||||
|
||||
`~/.olp/config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"allow_anonymous": false,
|
||||
"owner_only_endpoints": [
|
||||
"/health",
|
||||
"/v0/management/dashboard-data",
|
||||
"/v0/management/quota",
|
||||
"/v0/management/status",
|
||||
"/cache/stats",
|
||||
"/dashboard"
|
||||
],
|
||||
"fallback_detail_header_policy": "owner_only"
|
||||
},
|
||||
"providers": {
|
||||
"enabled": { "anthropic": true, "openai": true }
|
||||
},
|
||||
"routing": {
|
||||
"chains": {
|
||||
"claude-sonnet-4-6": [
|
||||
{ "provider": "anthropic", "model": "claude-sonnet-4-6" },
|
||||
{ "provider": "openai", "model": "gpt-5.5" }
|
||||
],
|
||||
"gpt-5.5": [{ "provider": "openai", "model": "gpt-5.5" }]
|
||||
}
|
||||
},
|
||||
"streaming": { "heartbeat_interval_ms": 15000 }
|
||||
}
|
||||
```
|
||||
|
||||
(Enable only the providers you actually authenticated in step 3. Chains map `<your-IDE's-requested-model>` → ordered list of `{provider, model}` hops; the chain's per-hop `model` is what gets passed to that provider's CLI.)
|
||||
|
||||
### 5. Start the server
|
||||
|
||||
```bash
|
||||
cd ~/olp
|
||||
npm start
|
||||
# OLP v0.4.3 listening on :4567 (2 providers enabled)
|
||||
```
|
||||
|
||||
### 6. Smoke-test
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $OLP_API_KEY" http://localhost:4567/health | jq
|
||||
# Expect: {ok: true, providers: {enabled: 2, status: {anthropic: {ok: true...}, openai: {ok: true...}}}}
|
||||
|
||||
node ~/olp/bin/olp.mjs doctor
|
||||
# Expect: "9 of 9 checks passed", kind=noop
|
||||
```
|
||||
|
||||
### 7. Point your IDE at OLP
|
||||
|
||||
```
|
||||
OPENAI_BASE_URL=http://localhost:4567/v1
|
||||
OPENAI_API_KEY=$OLP_API_KEY
|
||||
```
|
||||
|
||||
Per-IDE configuration details: [`docs/integrations/`](./docs/integrations/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Family / LAN setup
|
||||
|
||||
To let other devices on your home network use the same OLP server, you need TWO things:
|
||||
|
||||
1. **Bind to the LAN interface** (not just loopback). On the SERVER:
|
||||
|
||||
```bash
|
||||
OLP_BIND=0.0.0.0 npm start # or your specific LAN IP, e.g. 192.168.1.10
|
||||
```
|
||||
|
||||
Default is `127.0.0.1` (loopback only). See [ADR 0011 § Deployment configurations](./docs/adr/0011-anonymous-key-deployment-context.md#deployment-configurations-d76-amendment-2026-05-26) for the trust-context table — **never set `OLP_BIND=0.0.0.0` on a public-internet-facing host** (use a tunnel like Tailscale instead).
|
||||
|
||||
2. **Onboard each family member's device** from THEIR machine:
|
||||
|
||||
```bash
|
||||
# Pinned to a known-good release (recommended — survives GitHub raw CDN cache hiccups):
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/dtzp555-max/olp/v0.4.4/bin/olp-connect) <olp-host-ip>
|
||||
|
||||
# OR latest from main (use after v0.4.4 + once you trust head):
|
||||
bash <(curl -fsSL https://raw.githubusercontent.com/dtzp555-max/olp/main/bin/olp-connect) <olp-host-ip>
|
||||
```
|
||||
|
||||
Detects Cline / Continue.dev / Cursor / Aider / OpenClaw locally and writes per-tool config pointing at your OLP host. Requires `python3` on the client. Prompts for the OLP API key — OR, if the server has `auth.advertise_anonymous_key: true` AND a key was created with `olp-keys keygen --anonymous --advertise`, picks the token up from `/health.anonymousKey` (zero out-of-band paste). See [ADR 0011](./docs/adr/0011-anonymous-key-deployment-context.md) for the trusted-LAN-only invariant.
|
||||
|
||||
Per-IDE setup details: [`docs/integrations/`](./docs/integrations/README.md). Telegram / Discord `/olp` slash command setup: [§ Telegram / Discord Usage](#telegram--discord-usage).
|
||||
|
||||
---
|
||||
|
||||
@@ -80,12 +233,19 @@ OLP distinguishes **Candidate Providers** (declared as intended, not yet pinned)
|
||||
|
||||
## Configuration
|
||||
|
||||
_placeholder — full configuration reference lands with Phase 4 (fallback engine)._
|
||||
|
||||
OLP reads its config from `~/.olp/config.json`. The minimum useful shape:
|
||||
OLP reads `~/.olp/config.json` at startup. § "[Manual install § Step 4](#4-write-a-minimum-config)" above has a working minimum example. The full schema:
|
||||
|
||||
```json
|
||||
{
|
||||
"auth": {
|
||||
"allow_anonymous": false,
|
||||
"owner_only_endpoints": ["/health", "/dashboard", "/v0/management/..."],
|
||||
"advertise_anonymous_key": false,
|
||||
"fallback_detail_header_policy": "owner_only"
|
||||
},
|
||||
"providers": {
|
||||
"enabled": { "<provider-key>": true }
|
||||
},
|
||||
"routing": {
|
||||
"chains": {
|
||||
"<requested-model>": [
|
||||
@@ -96,13 +256,25 @@ OLP reads its config from `~/.olp/config.json`. The minimum useful shape:
|
||||
"soft_triggers": {
|
||||
"<provider-key>": { "<trigger>": <threshold> }
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"heartbeat_interval_ms": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** `routing.soft_triggers` thresholds are parsed and stored but have **no runtime effect at v0.1** — the quota polling path (`quotaStatus()` per hop) is deferred to v1.x per [ADR 0004 Amendment 2](./docs/adr/0004-fallback-engine.md#amendment-2--2026-05-24-soft-triggers-deferred-to-v1x-d22). The evaluation logic exists and is tested; only the production data ingestion path is deferred.
|
||||
Field guide:
|
||||
|
||||
Trigger types, fallback safety, idempotency rules, and the full example config land here when Phase 4 ships. See [ADR 0004 (Fallback Engine Semantics & Safety)](./docs/adr/0004-fallback-engine.md) for the design.
|
||||
- **`auth.allow_anonymous`** — default `false`. When false, every request needs a Bearer token; when true, anonymous-tier requests succeed (ADR 0007 § 7). Production posture is `false`.
|
||||
- **`auth.owner_only_endpoints`** — list of endpoints that REQUIRE owner-tier auth (non-owner returns 401). The defaults above are minimum sane for production.
|
||||
- **`auth.advertise_anonymous_key`** — default `false`. When true (+ `allow_anonymous: true` + a key created with `olp-keys keygen --anonymous --advertise`), `/health.anonymousKey` exposes the plaintext token so `olp-connect <ip>` is zero-config. **Trusted-LAN only** — see [ADR 0011](./docs/adr/0011-anonymous-key-deployment-context.md).
|
||||
- **`auth.fallback_detail_header_policy`** — controls `X-OLP-Fallback-Detail` response header emission. `owner_only` (default) only shows tuples to owner identity; debug surface to LAN family without leaking to anonymous.
|
||||
- **`providers.enabled`** — flip a provider plugin on. Only enable providers whose CLI you've authenticated; OLP doesn't do its own OAuth.
|
||||
- **`routing.chains`** — keyed by the model name your IDE / client requests. Each entry is an ordered list of fallback hops; each hop's `model` is what gets passed to that provider's CLI. F7 fix (D75) — the hop-level `model` field finally overrides the IR's request model during cross-provider fallback.
|
||||
- **`routing.soft_triggers`** — parsed and stored but **inert at v0.4.x** — the `quotaStatus()` polling data path is deferred to v1.x per [ADR 0004 Amendment 2](./docs/adr/0004-fallback-engine.md#amendment-2--2026-05-24-soft-triggers-deferred-to-v1x-d22). Startup emits a warn if non-empty so the inert state is visible.
|
||||
- **`streaming.heartbeat_interval_ms`** — default `0` (disabled). Set > 0 (e.g. `15000`) to emit SSE keepalive frames during silent windows. Required behind reverse proxies (nginx / Cloudflare Tunnel / Tailscale Funnel) with 60s idle aborts.
|
||||
|
||||
See [ADR 0004 (Fallback Engine)](./docs/adr/0004-fallback-engine.md), [ADR 0007 (Multi-key auth)](./docs/adr/0007-multi-key-auth.md), [ADR 0010 (Phase 4 charter)](./docs/adr/0010-phase-4-charter-operator-and-client-ux.md), [ADR 0011 (Anonymous-key deployment)](./docs/adr/0011-anonymous-key-deployment-context.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -127,6 +299,10 @@ _placeholder — full table lands per-phase as variables are introduced._
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `OLP_PORT` | `4567` | HTTP listener port. Moved off `3456` at D60 / v0.4.0 to co-host with OCP — set `OLP_PORT=3456` to restore the pre-D60 default. |
|
||||
| `OLP_BIND` | `127.0.0.1` | HTTP listener bind address. **Set to `0.0.0.0` or your LAN IP to accept LAN connections** (required for `olp-connect <ip>` to actually reach the server). Default loopback-only is the secure default. See [ADR 0011 § Deployment configurations](./docs/adr/0011-anonymous-key-deployment-context.md#deployment-configurations-d76-amendment-2026-05-26) for the trust-context table — never bind to a public-internet IP. |
|
||||
| `OLP_API_KEY` | (none) | Owner-tier OLP API key (the `olp_...` plaintext from `olp-keys keygen --owner`) used by `olp` CLI subcommands as the bearer for management endpoints. |
|
||||
| `OLP_OWNER_TOKEN` | (none) | Fallback used by `olp` CLI if `OLP_API_KEY` is absent. |
|
||||
| `OLP_PROXY_URL` | `http://127.0.0.1:$OLP_PORT` | Override target URL for `olp` CLI subcommands (so the same binary works against a remote OLP via SSH tunnel or direct LAN). |
|
||||
| `OLP_CLAUDE_BIN` | `claude` (from PATH) | Override path to the `claude` binary (Anthropic provider). Useful when multiple `claude` installs are present. |
|
||||
| `OLP_CODEX_BIN` | `codex` (from PATH) | Override path to the `codex` binary (OpenAI provider). |
|
||||
| `OLP_VIBE_BIN` | `vibe` (from PATH) | Override path to the `vibe` binary (Mistral provider). |
|
||||
@@ -353,16 +529,22 @@ Full spec (decision rationale, open questions, risks): `~/.cc-rules/memory/proje
|
||||
|
||||
## Migration from OCP
|
||||
|
||||
OLP is OCP's successor. The trigger was Anthropic's 2026-05-14 announcement (effective 2026-06-15) splitting `claude -p` / Agent SDK / third-party agent traffic out of the Pro/Max subscription pool into a separate fixed $100/month Agent SDK credit pool — invalidating OCP's foundational assumption (*"subscription = unlimited within rate limits"*) for its only provider. OLP's structural response is to spread risk across multiple subscriptions whose CLI/programmatic use remains in their main subscription pool, with intelligent fallback when one runs out.
|
||||
|
||||
Beyond the billing trigger, OLP is intentionally NOT a commercial multi-tenant SaaS (LiteLLM / OpenRouter / Portkey already serve that market with funding + SOC2), NOT an enterprise gateway competing on provider breadth, NOT a model-capability router ("route to the smartest model" — you pick the model in `routing.chains`), and NOT a conversation-state store (your client manages its own context). See [ADR 0001](./docs/adr/0001-project-founding.md) for the founding decision and [`ALIGNMENT.md`](./ALIGNMENT.md) for the constitution that governs every plugin / IR / entry-surface change.
|
||||
|
||||
### Migrating an existing OCP install
|
||||
|
||||
_placeholder — `scripts/migrate-from-ocp.mjs` lands with Phase 7 (📋 planned, not yet authored)._
|
||||
|
||||
Anticipated user-facing flow (target: <5 minutes):
|
||||
|
||||
1. Stop OCP (`launchctl bootout` the OCP service or `ocp stop`).
|
||||
2. Install OLP.
|
||||
3. Run `olp migrate-from-ocp` — copies `~/.ocp/keys/` to `~/.olp/keys/` and points provider plugins at OCP's existing auth artifacts where applicable.
|
||||
4. Start OLP. Clients pointing at port 4567 (or 3456 with `OLP_PORT=3456`) keep working; their existing OLP API keys remain valid. **Note (v0.4.0+):** default port moved from 3456 → 4567 so OCP and OLP can co-host during migration; set `OLP_PORT=3456` if you want the pre-D60 default.
|
||||
2. Install OLP (per [§ Manual install](#manual-install-5-10-min) above).
|
||||
3. Run `olp migrate-from-ocp` — will copy `~/.ocp/keys/` to `~/.olp/keys/` and point provider plugins at OCP's existing auth artifacts where applicable.
|
||||
4. Start OLP. Clients pointing at port 4567 (or 3456 with `OLP_PORT=3456`) keep working; their existing OLP API keys remain valid.
|
||||
|
||||
OCP's cache directory is *not* migrated: OLP's cache key format includes provider+model and warms cold naturally. OCP enters maintenance mode (stability fixes only) when OLP v0.1 ships; new development happens in OLP.
|
||||
**Default port moved 3456 → 4567 at v0.4.0** so OCP and OLP can co-host on the same machine during the migration window — set `OLP_PORT=3456` if you want the pre-D60 default. OCP's cache directory is *not* migrated: OLP's cache key format includes provider+model and warms cold naturally. OCP enters maintenance mode (stability fixes only) when OLP v0.1 ships; new development happens in OLP.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+47
-8
@@ -29,7 +29,35 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OLP_CONNECT_VERSION="0.4.0-phase4"
|
||||
# D78 (G13): derive version from package.json instead of hardcoding (was
|
||||
# stuck at "0.4.0-phase4" through v0.4.1/v0.4.2/v0.4.3 because no one
|
||||
# updated it). Look up package.json next to the script if available;
|
||||
# fall back to "unknown" when running curl-piped (no on-disk package.json).
|
||||
_resolve_version() {
|
||||
local script_dir pkg
|
||||
# When curl-piped (`curl ... | bash`), BASH_SOURCE[0] is empty → dirname
|
||||
# yields "." → script_dir resolves to cwd. D78 reviewer P2-1 hardening:
|
||||
# require the suffix-strip to actually fire (script_dir ENDED with /bin),
|
||||
# otherwise we'd happily pick up an unrelated package.json from whatever
|
||||
# directory the user happens to be in when piping. Belt-and-braces.
|
||||
# ${BASH_SOURCE[0]:-} default-empty guards against `set -u` nounset error
|
||||
# when invoked via `curl ... | bash` (no source file → BASH_SOURCE unset).
|
||||
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]:-}")" &>/dev/null && pwd)"
|
||||
if [[ "$script_dir" != */bin ]]; then
|
||||
echo "unknown"
|
||||
return
|
||||
fi
|
||||
pkg="${script_dir%/bin}/package.json"
|
||||
# D78 reviewer P2-2: pass $pkg via env var instead of -c interpolation
|
||||
# so paths with apostrophes / shell metacharacters can't break the
|
||||
# python invocation. Canonical layout is safe; this is defense-in-depth.
|
||||
if [[ -f "$pkg" ]] && command -v python3 >/dev/null 2>&1; then
|
||||
OLP_PKG_PATH="$pkg" python3 -c 'import json,os;print(json.load(open(os.environ["OLP_PKG_PATH"])).get("version","unknown"))' 2>/dev/null || echo "unknown"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
OLP_CONNECT_VERSION="$(_resolve_version)"
|
||||
|
||||
show_version() {
|
||||
echo "olp-connect $OLP_CONNECT_VERSION"
|
||||
@@ -246,17 +274,28 @@ detect_aider() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Detect OpenClaw. Per Phase 4 D71-D73 (NOT in this PR), olp will ship
|
||||
# olp-plugin/ for OpenClaw with full Telegram/Discord /olp slash commands.
|
||||
# Until that ships, we just announce detection and link.
|
||||
# Detect OpenClaw. Phase 4 D71-D73 shipped olp-plugin/ as the OpenClaw
|
||||
# gateway plugin for /olp Telegram + Discord slash commands. Point users
|
||||
# at the install path.
|
||||
detect_openclaw() {
|
||||
if command -v openclaw &>/dev/null || [[ -f "$HOME/.openclaw/openclaw.json" ]]; then
|
||||
log_info ""
|
||||
log_info "Detected: OpenClaw"
|
||||
log_info " The OpenClaw OLP plugin (D71-D73) is NOT YET SHIPPED."
|
||||
log_info " When it ships, install with: openclaw plugin install olp"
|
||||
log_info " For now, you can manually point OpenClaw at OLP via the OPENAI_BASE_URL"
|
||||
log_info " env var (already written to your shell rc above)."
|
||||
log_info " OLP ships an OpenClaw gateway plugin for /olp Telegram + Discord"
|
||||
log_info " slash commands (status / usage / cache / models / providers /"
|
||||
log_info " chain show / health / doctor). Read-only by design — no chat-side"
|
||||
log_info " mutations."
|
||||
log_info ""
|
||||
log_info " Install the plugin (one-time, on the host running OpenClaw):"
|
||||
log_info " git clone https://github.com/dtzp555-max/olp.git /tmp/olp-repo"
|
||||
log_info " openclaw plugins install /tmp/olp-repo/olp-plugin"
|
||||
log_info " # OR symlink: ln -sf /tmp/olp-repo/olp-plugin ~/.openclaw/extensions/olp"
|
||||
log_info ""
|
||||
log_info " Then edit ~/.openclaw/openclaw.json to set the plugin apiKey to a"
|
||||
log_info " dedicated OLP key (NOT your owner key — create one via olp-keys"
|
||||
log_info " keygen --name <bot-name>). Restart OpenClaw gateway."
|
||||
log_info ""
|
||||
log_info " See docs/integrations/openclaw.md for full instructions."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,24 @@ alongside the "using server-advertised key" notice.
|
||||
|
||||
---
|
||||
|
||||
## Deployment configurations (D76 amendment, 2026-05-26)
|
||||
|
||||
Original ADR 0011 referenced a `BIND_ADDRESS` concept that did not exist in the v0.4.0–v0.4.2 codebase — the server was hard-coded to `server.listen(PORT, '127.0.0.1', ...)`. D76 closes this gap by adding the `OLP_BIND` env var (default `127.0.0.1`), making the deployment-context discussion below operational rather than aspirational.
|
||||
|
||||
Three deployment configurations are supported:
|
||||
|
||||
| `OLP_BIND` value | Reachability | Anonymous-key publication |
|
||||
|---|---|---|
|
||||
| `127.0.0.1` (default) | Loopback only | Safe with any auth posture (no LAN exposure at all) |
|
||||
| RFC1918 IP / tailnet IP / `0.0.0.0` on a trusted LAN | LAN clients only | Safe when `advertise_anonymous_key: true` — the documented "trusted-LAN" zero-config family onboarding flow |
|
||||
| Public IP / `0.0.0.0` on a public-facing host | Public internet | **Incompatible with `advertise_anonymous_key: true`.** Operator MUST keep `advertise_anonymous_key: false` (default). |
|
||||
|
||||
The server emits a startup warn event `anonymous_key_advertised_with_lan_bind` when `OLP_BIND` is non-loopback AND `advertise_anonymous_key: true` (per the `lib/keys.mjs` + `server.mjs` checks). The warn is a **checkpoint, not a hard gate** — the server cannot tell from the bind address alone whether the operator is on a trusted LAN (RFC1918 / tailnet) or has accidentally exposed a public IP. The Re-evaluation trigger #1 below escalates to a hard gate when OLP gains a public-internet deployment mode.
|
||||
|
||||
`olp-connect <ip>` consumes `/health.anonymousKey` over the network — therefore requires `OLP_BIND` to include the LAN interface on the server side. Without setting `OLP_BIND=<lan-ip>` (or `0.0.0.0`), `olp-connect <ip>` will fail with `connect ECONNREFUSED` because the server only accepts loopback connections.
|
||||
|
||||
---
|
||||
|
||||
## Re-evaluation triggers
|
||||
|
||||
Re-open this ADR when ANY of the following fires:
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "olp",
|
||||
"version": "0.4.2",
|
||||
"version": "0.4.4",
|
||||
"description": "Personal multi-provider LLM proxy. Successor to OCP. One HTTP endpoint, multiple subscriptions behind it, automatic routing + fallback + caching.",
|
||||
"type": "module",
|
||||
"main": "server.mjs",
|
||||
|
||||
+30
-1
@@ -18,6 +18,14 @@
|
||||
* so OLP can co-host with OCP for migration windows. ADR 0010 §
|
||||
* Default port. Set OLP_PORT=3456 explicitly to restore the
|
||||
* pre-D60 default when not co-hosting with OCP.)
|
||||
* OLP_BIND — listen address (default: 127.0.0.1 since D76 / v0.4.3). Set
|
||||
* to 0.0.0.0 (or a specific interface IP) to accept connections
|
||||
* from LAN clients (required for olp-connect <ip> to actually
|
||||
* reach the server). Server emits a startup warn if BIND
|
||||
* resolves to a non-loopback address AND auth.allow_anonymous
|
||||
* is true (anonymous-key over LAN may be acceptable; anonymous-
|
||||
* key over public internet is not — see ADR 0011 § Deployment
|
||||
* configurations).
|
||||
*/
|
||||
|
||||
import { createServer } from 'node:http';
|
||||
@@ -77,6 +85,14 @@ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
|
||||
const VERSION = pkg.version;
|
||||
|
||||
const PORT = parseInt(process.env.OLP_PORT ?? '4567', 10);
|
||||
// F5 / D76: OLP_BIND env. Defaults to 127.0.0.1 (loopback only — secure
|
||||
// default). Operators expose LAN by setting OLP_BIND=0.0.0.0 (or a specific
|
||||
// interface). Per ADR 0011 § Deployment configurations:
|
||||
// - 127.0.0.1: trusted single-machine; safe with any auth posture
|
||||
// - RFC1918 / tailnet / specific LAN IP: trusted-LAN — anonymous_key OK
|
||||
// - 0.0.0.0: ALL interfaces — operator MUST ensure auth posture matches the
|
||||
// network reachability (e.g. no advertise_anonymous_key on public IP)
|
||||
const BIND = process.env.OLP_BIND ?? '127.0.0.1';
|
||||
const BODY_LIMIT = 5 * 1024 * 1024; // 5 MB
|
||||
|
||||
// ── Logging ───────────────────────────────────────────────────────────────
|
||||
@@ -253,6 +269,19 @@ if (_authConfig.advertise_anonymous_key === true) {
|
||||
message: 'auth.advertise_anonymous_key=true but no active key with plaintext_advertise exists. Run `olp-keys keygen --anonymous --advertise` to create one. /health.anonymousKey will NOT be emitted until then. See ADR 0011.',
|
||||
});
|
||||
}
|
||||
// F5 / D76 (ADR 0011 Deployment configurations): publishing the anonymous
|
||||
// key via /health is only safe when /health is reachable ONLY from a
|
||||
// trusted network. If OLP_BIND is set to a non-loopback address AND
|
||||
// advertise_anonymous_key is true, warn the operator. We can't tell from
|
||||
// here whether the non-loopback bind is "trusted LAN" (RFC1918 / tailnet)
|
||||
// or "public internet" — that's the operator's responsibility. The warn is
|
||||
// a checkpoint, not a hard gate.
|
||||
if (BIND !== '127.0.0.1' && BIND !== 'localhost' && BIND !== '::1') {
|
||||
logEvent('warn', 'anonymous_key_advertised_with_lan_bind', {
|
||||
message: `auth.advertise_anonymous_key=true with OLP_BIND=${BIND} — /health.anonymousKey will be reachable from any host that can connect to ${BIND}:${PORT}. Confirm this address is on a trusted LAN (RFC1918 / tailnet) — never a public IP. See ADR 0011 § Deployment configurations.`,
|
||||
bind: BIND,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal — test seam: inject a synthetic auth config (no file I/O). */
|
||||
@@ -2258,7 +2287,7 @@ const isMain = (() => {
|
||||
|
||||
if (isMain) {
|
||||
const server = createOlpServer();
|
||||
server.listen(PORT, '127.0.0.1', () => {
|
||||
server.listen(PORT, BIND, () => {
|
||||
const enabledCount = loadedProviders.size;
|
||||
// D74 P3-5: banner no longer hardcodes the phase. Derives from VERSION
|
||||
// (which advances at every Phase close) so banner stays accurate
|
||||
|
||||
@@ -15361,4 +15361,82 @@ describe('Suite 36 — D74 v0.4.1 hotfix regression (maintainer review findings)
|
||||
resetAC36r();
|
||||
}
|
||||
});
|
||||
|
||||
// ── F5 (D76 v0.4.3): OLP_BIND env honored + safety warn ──────────────────
|
||||
it('36s (F5) — server.mjs reads OLP_BIND with safe default 127.0.0.1', () => {
|
||||
// F5 fix: previously bind was hard-coded `server.listen(PORT, '127.0.0.1', ...)`.
|
||||
// D76 adds `const BIND = process.env.OLP_BIND ?? '127.0.0.1'` and the listen
|
||||
// call uses BIND. Pin via source grep so a future refactor that re-introduces
|
||||
// a hardcoded literal in the listen call fails this test.
|
||||
const serverSrc = _readFileSyncS36(_joinS36(import.meta.dirname ?? process.cwd(), 'server.mjs'), 'utf8');
|
||||
assert.ok(/process\.env\.OLP_BIND\s*\?\?\s*['"]127\.0\.0\.1['"]/.test(serverSrc),
|
||||
'server.mjs must read OLP_BIND env with 127.0.0.1 default');
|
||||
assert.ok(/server\.listen\(PORT,\s*BIND\b/.test(serverSrc),
|
||||
'server.mjs server.listen must use the BIND variable (not a hardcoded address)');
|
||||
assert.ok(!/server\.listen\(PORT,\s*['"]127\.0\.0\.1['"]/.test(serverSrc),
|
||||
'server.mjs must NOT pass a hardcoded 127.0.0.1 literal to server.listen anymore');
|
||||
});
|
||||
|
||||
it('36t (F5) — anonymous_key_advertised_with_lan_bind startup warn wiring', () => {
|
||||
// Per ADR 0011 Deployment configurations amendment: when OLP_BIND is
|
||||
// non-loopback AND advertise_anonymous_key is true, server emits a startup
|
||||
// warn so the operator sees the trust-context overlap. Pin the wiring.
|
||||
const serverSrc = _readFileSyncS36(_joinS36(import.meta.dirname ?? process.cwd(), 'server.mjs'), 'utf8');
|
||||
assert.ok(/anonymous_key_advertised_with_lan_bind/.test(serverSrc),
|
||||
'startup warn event name must be present');
|
||||
// Loopback check must compare BIND against ALL three loopback forms
|
||||
assert.ok(/BIND\s*!==\s*['"]127\.0\.0\.1['"][\s\S]{0,200}BIND\s*!==\s*['"]localhost['"][\s\S]{0,200}BIND\s*!==\s*['"]::1['"]/.test(serverSrc),
|
||||
'startup warn must check BIND against all three loopback forms (127.0.0.1 / localhost / ::1)');
|
||||
});
|
||||
|
||||
it('36u (F5) — ADR 0011 Deployment configurations amendment present', () => {
|
||||
const adrPath = _joinS36(import.meta.dirname ?? process.cwd(), 'docs/adr/0011-anonymous-key-deployment-context.md');
|
||||
const adrSrc = _readFileSyncS36(adrPath, 'utf8');
|
||||
assert.ok(/Deployment configurations \(D76 amendment/.test(adrSrc),
|
||||
'ADR 0011 must carry the D76 amendment heading');
|
||||
assert.ok(/OLP_BIND/.test(adrSrc), 'amendment must document OLP_BIND');
|
||||
assert.ok(/anonymous_key_advertised_with_lan_bind/.test(adrSrc),
|
||||
'amendment must cite the startup-warn event name');
|
||||
});
|
||||
|
||||
// ── D78 v0.4.4: G12 stale openclaw text + G13 self-version derived ──────
|
||||
it('36v (G12) — olp-connect openclaw detection no longer claims plugin not shipped', () => {
|
||||
// D71-D73 shipped olp-plugin/. Pre-D78 the script said "NOT YET SHIPPED"
|
||||
// which misled MacBook client testing on 2026-05-26. Pin the corrected text.
|
||||
const ocSrc = _readFileSyncS36(_joinS36(import.meta.dirname ?? process.cwd(), 'bin/olp-connect'), 'utf8');
|
||||
assert.ok(!/NOT YET SHIPPED/.test(ocSrc),
|
||||
'olp-connect must NOT claim openclaw plugin is unshipped (D71-D73 shipped it at v0.4.0)');
|
||||
assert.ok(/openclaw plugins install/.test(ocSrc) || /\.openclaw\/extensions\/olp/.test(ocSrc),
|
||||
'olp-connect openclaw detection must give a real install path');
|
||||
assert.ok(/docs\/integrations\/openclaw\.md/.test(ocSrc),
|
||||
'olp-connect must point at the openclaw integration doc');
|
||||
});
|
||||
|
||||
it('36w (G13) — olp-connect self-version derived from package.json (not hardcoded)', () => {
|
||||
// Pre-D78 OLP_CONNECT_VERSION was a hardcoded literal "0.4.0-phase4" that
|
||||
// nobody updated across v0.4.1/v0.4.2/v0.4.3. D78 derives at runtime
|
||||
// from sibling package.json so the literal stays in sync automatically.
|
||||
const ocSrc = _readFileSyncS36(_joinS36(import.meta.dirname ?? process.cwd(), 'bin/olp-connect'), 'utf8');
|
||||
// The old hardcoded literal must be gone
|
||||
assert.ok(!/OLP_CONNECT_VERSION="0\.4\.0-phase4"/.test(ocSrc),
|
||||
'hardcoded "0.4.0-phase4" version literal must be gone');
|
||||
// The new derivation logic must reference package.json
|
||||
assert.ok(/package\.json/.test(ocSrc) && /OLP_CONNECT_VERSION=/.test(ocSrc),
|
||||
'OLP_CONNECT_VERSION must derive from package.json');
|
||||
});
|
||||
|
||||
it('36x (D78) — README pins primary olp-connect curl URL to a release tag (CDN-cache-safe)', () => {
|
||||
// G11 root cause: README's `bash <(curl ... /main/bin/olp-connect)` got
|
||||
// bitten by GitHub raw CDN's negative-cache TTL when the repo flipped
|
||||
// private->public. Tag-pinned URLs (.../<tag>/bin/...) bypass that
|
||||
// negative cache because the tag ref was never queried while private.
|
||||
// D78: README presents the tag-pinned URL as the primary recommendation,
|
||||
// with /main/ as an alternative for trusted-head users.
|
||||
const readmePath = _joinS36(import.meta.dirname ?? process.cwd(), 'README.md');
|
||||
const readmeSrc = _readFileSyncS36(readmePath, 'utf8');
|
||||
assert.ok(/raw\.githubusercontent\.com\/dtzp555-max\/olp\/v\d+\.\d+\.\d+\/bin\/olp-connect/.test(readmeSrc),
|
||||
'README must include a release-tag-pinned olp-connect URL (e.g., /v0.4.4/bin/olp-connect)');
|
||||
assert.ok(/Pinned to a known-good release/.test(readmeSrc),
|
||||
'README must explain why the tag-pinned form is the primary recommendation');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user