Compare commits

...
Author SHA1 Message Date
taodengandClaude <claude-opus-4-8> <noreply@anthropic.com> bb48374f84 docs(readme): date-stamp the billing-split status (PAUSED) + scope LAN mode to chat-class workloads
P0 from the README audit, direction set by the maintainer's history lesson: the
2026-06-15 -p billing split was announced (2026-05-14) but PAUSED by Anthropic on
the effective date — officially: "For now, nothing has changed: Claude Agent SDK,
claude -p, and third-party app usage still draw from your subscription's usage
limits" (support.claude.com article 15036540). The README asserted the split as
in-force in five places while the top-of-README "$0 / no API billing" pitch said
the opposite — a self-contradiction where the PITCH was the currently-true half.

- How It Works: added the dated billing-policy status note (official quote + link);
  the "$0" claims stand, now anchored to it.
- TUI § "What it is and why": status banner; the cc_entrypoint table relabeled
  "announced regime, currently paused"; TUI-mode reframed as the ready-made HEDGE
  for if/when a reworked change lands (advance notice promised).
- "2026-06-15 operator checklist" -> "Operator checklist for the (paused) billing
  split": nothing needs flipping while the pause holds; checklist retained verbatim
  as the re-landing runbook.
- OCP_SKIP_AUTH_TEST env row + /health tui-block prose: conditioned on the paused
  regime instead of asserting it.
- Client-tools boundary: new "workload fit" paragraph (maintainer-approved
  positioning): LAN/multi-device OCP = chat-class workloads; client-machine
  coding agents are architecturally out of scope (tools execute on the OCP host)
  — run claude/OCP on the machine where the code lives.

Lesson encoded: policy-dependent claims carry dates and the official source, so
the next policy swing is an edit, not an archaeology dig.

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>
2026-07-17 09:34:29 +10:00
dtzp555-maxGitHubtaodengClaude <claude-opus-4-8> <noreply@anthropic.com>
6f18613f9d docs(readme): staleness sweep — model count/examples, phantom ocp stop, undocumented env vars, ocp-connect claim (#170)
* docs(readme): sweep stale content — 6 models, drop phantom `ocp stop`, document 2 live env vars, fix ocp-connect claim

P1 findings from a full README-vs-code staleness audit (each verified against the
tree at 0c3e42b):

- "5 models" x4 (L124/151/174/323) -> 6; the /v1/models curl example (L244) and
  ocp-connect sample output (L353) now include claude-sonnet-5 (added #152).
- Troubleshooting told users to run `ocp stop`, which has never existed (the ocp
  case table has no stop) -> replaced with the real launchctl/systemctl commands
  + a note that stopping goes through the service manager.
- CLAUDE_SYSTEM_PROMPT (server.mjs:326, applied at :1084) and CLAUDE_MCP_CONFIG
  (server.mjs:1124 -p path; lib/tui/session.mjs:447 FULL_TOOLS panes) are live
  config with no env-table row -> added both rows.
- "ocp-connect detects and configures Claude Code, Cursor, ..." over-claimed:
  it auto-configures OpenClaw only, prints hints for Cursor/Cline/Continue/opencode,
  and has no Claude Code logic at all (OCP exposes an OpenAI-compat surface;
  Claude Code speaks the Anthropic protocol) -> reworded L28 + removed Claude Code
  from the client-connect prompt template (L160).
- Upgrade examples presented v3.14 as the frontier -> refreshed to current-era
  numbers (v3.21.0->v3.21.1 patch, v3.18->v3.22 cross-minor). Historical feature
  attributions ("as of v3.14.0", bootstrap-quirk notes) kept — they are facts.

Docs only; no code change. P0 (billing status note) and P2 (structure) are
tracked separately.

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>

* docs(readme): drop the CLAUDE_SYSTEM_PROMPT row (dead env var) + fix systemd stop to --user

Reviewer traced consumers: SYSTEM_PROMPT (server.mjs:326) is only echoed on
/health (:2906) and the startup log (:3270) — extractSystemPrompt() never reads
it, so the row documented behavior that does not exist. The dead var + the two
stale in-code comments (server.mjs:19, :1084) go on the backlog: wire it or
remove it (a server.mjs change, out of this docs PR's scope).

Also: setup.mjs installs the systemd unit under --user (:514), so the
Troubleshooting stop line loses the sudo and gains --user.

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude <claude-opus-4-8> <noreply@anthropic.com>
2026-07-17 09:30:57 +10:00
+30 -20
View File
@@ -25,7 +25,7 @@ One proxy. Multiple IDEs. All models. **$0 API cost.**
There are several Claude proxy projects. OCP picks a specific lane: **align tightly with what `cli.js` actually does, observe + multiplex what's already there, don't extend the protocol.** What you get: There are several Claude proxy projects. OCP picks a specific lane: **align tightly with what `cli.js` actually does, observe + multiplex what's already there, don't extend the protocol.** What you get:
- **LAN multi-user keys** (v3.7.0) — reach one Claude Pro/Max subscription from your own devices across the LAN. Each device gets a per-key API token (no OAuth session leak), with independent usage tracking and one-line revocation. Pro/Max are **per-user** accounts — see [Sharing with family / a team — honest limits](#deployment-model--security-read-this) before extending access to other **people**. - **LAN multi-user keys** (v3.7.0) — reach one Claude Pro/Max subscription from your own devices across the LAN. Each device gets a per-key API token (no OAuth session leak), with independent usage tracking and one-line revocation. Pro/Max are **per-user** accounts — see [Sharing with family / a team — honest limits](#deployment-model--security-read-this) before extending access to other **people**.
- **`ocp-connect` one-shot IDE setup** — one command on the client machine detects and configures Claude Code, Cursor, Cline, Continue.dev, OpenCode, and OpenClaw. No pasting `OPENAI_BASE_URL` six times. - **`ocp-connect` one-shot client setup** — one command on the client machine auto-configures OpenClaw, and detects Cursor, Cline, Continue.dev, and opencode to print ready-to-paste setup hints for each. No hunting for where each tool keeps its `OPENAI_BASE_URL`.
- **Response cache with per-key isolation + singleflight** (v3.13.0). Optional SHA-256 prompt cache, isolated per API key (cross-user pollution is impossible by hash construction, not by application logic), with stampede protection on concurrent identical prompts. Off by default. ([PR #65](https://github.com/dtzp555-max/ocp/pull/65), [PR #66](https://github.com/dtzp555-max/ocp/pull/66)) - **Response cache with per-key isolation + singleflight** (v3.13.0). Optional SHA-256 prompt cache, isolated per API key (cross-user pollution is impossible by hash construction, not by application logic), with stampede protection on concurrent identical prompts. Off by default. ([PR #65](https://github.com/dtzp555-max/ocp/pull/65), [PR #66](https://github.com/dtzp555-max/ocp/pull/66))
- **Per-key request quotas** (v3.8.0). Daily / weekly / monthly limits per key — set a kid's iPad to 20/day, a partner's laptop to 100/week. ([PR #18](https://github.com/dtzp555-max/ocp/pull/18)) - **Per-key request quotas** (v3.8.0). Daily / weekly / monthly limits per key — set a kid's iPad to 20/day, a partner's laptop to 100/week. ([PR #18](https://github.com/dtzp555-max/ocp/pull/18))
- **SSE heartbeat for long reasoning** ([v3.12.0](https://github.com/dtzp555-max/ocp/releases/tag/v3.12.0), opt-in). If you've ever watched your IDE die at the 60s idle mark during a long Claude tool-use pause — that's nginx/Cloudflare default behavior. OCP emits an SSE comment frame to keep the connection alive without polluting the response. ([PR #49](https://github.com/dtzp555-max/ocp/pull/49)) - **SSE heartbeat for long reasoning** ([v3.12.0](https://github.com/dtzp555-max/ocp/releases/tag/v3.12.0), opt-in). If you've ever watched your IDE die at the 60s idle mark during a long Claude tool-use pause — that's nginx/Cloudflare default behavior. OCP emits an SSE comment frame to keep the connection alive without polluting the response. ([PR #49](https://github.com/dtzp555-max/ocp/pull/49))
@@ -121,7 +121,7 @@ Please follow https://github.com/dtzp555-max/ocp/blob/main/README.md
installed and logged in (`claude auth status`). Install missing pieces installed and logged in (`claude auth status`). Install missing pieces
using my system's package manager. using my system's package manager.
2. git clone the repo, cd in, and run `node setup.mjs`. 2. git clone the repo, cd in, and run `node setup.mjs`.
3. Verify with `curl http://127.0.0.1:3456/v1/models` (should list 5 models). 3. Verify with `curl http://127.0.0.1:3456/v1/models` (should list 6 models).
4. Add `export OPENAI_BASE_URL=http://127.0.0.1:3456/v1` to my shell rc. 4. Add `export OPENAI_BASE_URL=http://127.0.0.1:3456/v1` to my shell rc.
5. Tell me to reload my shell and try a tool like Cline / Continue / Cursor. 5. Tell me to reload my shell and try a tool like Cline / Continue / Cursor.
@@ -148,7 +148,7 @@ Please follow https://github.com/dtzp555-max/ocp/blob/main/README.md
5. Add OCP_ADMIN_KEY to my shell rc (~/.zshrc or ~/.bashrc). 5. Add OCP_ADMIN_KEY to my shell rc (~/.zshrc or ~/.bashrc).
6. Run `ocp lan` to show me the LAN IP and connect command. 6. Run `ocp lan` to show me the LAN IP and connect command.
7. Optionally create example keys: `ocp keys add laptop`, `ocp keys add tablet`. 7. Optionally create example keys: `ocp keys add laptop`, `ocp keys add tablet`.
8. Verify: `curl http://127.0.0.1:3456/v1/models` returns 5 models. 8. Verify: `curl http://127.0.0.1:3456/v1/models` returns 6 models.
Tell me each step before running it. On error, diagnose before retrying. Tell me each step before running it. On error, diagnose before retrying.
``` ```
@@ -157,8 +157,7 @@ Tell me each step before running it. On error, diagnose before retrying.
```text ```text
There's an OCP server at <SERVER_IP> on my LAN. Configure this machine to There's an OCP server at <SERVER_IP> on my LAN. Configure this machine to
use it for any local IDEs (Cursor, Cline, Continue.dev, OpenCode, Claude use it for any local IDEs (Cursor, Cline, Continue.dev, OpenCode, OpenClaw).
Code, OpenClaw).
Server IP: <SERVER_IP> Server IP: <SERVER_IP>
API key (leave blank if the server has anonymous mode enabled): <OPTIONAL_KEY> API key (leave blank if the server has anonymous mode enabled): <OPTIONAL_KEY>
@@ -171,7 +170,7 @@ Please follow https://github.com/dtzp555-max/ocp/blob/main/README.md
chmod +x ocp-connect chmod +x ocp-connect
2. Run `./ocp-connect <SERVER_IP>` (add `--key <KEY>` if you have one). 2. Run `./ocp-connect <SERVER_IP>` (add `--key <KEY>` if you have one).
3. Follow any IDE-specific manual hints it prints. 3. Follow any IDE-specific manual hints it prints.
4. Verify: `curl http://<SERVER_IP>:3456/v1/models` returns 5 models. 4. Verify: `curl http://<SERVER_IP>:3456/v1/models` returns 6 models.
5. Tell me to reload my shell + restart any IDE that was already running. 5. Tell me to reload my shell + restart any IDE that was already running.
Don't auto-retry on error. Tell me the failure mode first. Don't auto-retry on error. Tell me the failure mode first.
@@ -241,7 +240,7 @@ Run `ocp lan` to see your IP and ready-to-share instructions.
**Verify:** **Verify:**
```bash ```bash
curl http://127.0.0.1:3456/v1/models curl http://127.0.0.1:3456/v1/models
# Returns: claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5-20251001 # Returns: claude-opus-4-8, claude-opus-4-7, claude-opus-4-6, claude-sonnet-5, claude-sonnet-4-6, claude-haiku-4-5-20251001
``` ```
#### Headless install notes #### Headless install notes
@@ -320,7 +319,7 @@ OCP Connect v1.3.0
(set by admin via PROXY_ANONYMOUS_KEY; see issue #12 §14 Path A) (set by admin via PROXY_ANONYMOUS_KEY; see issue #12 §14 Path A)
Testing API access... Testing API access...
✓ API accessible (5 models available) ✓ API accessible (6 models available)
Shell config: Shell config:
✓ .bashrc ✓ .bashrc
@@ -353,6 +352,7 @@ OCP Connect v1.3.0
• ocp/claude-opus-4-8 • ocp/claude-opus-4-8
• ocp/claude-opus-4-7 • ocp/claude-opus-4-7
• ocp/claude-opus-4-6 • ocp/claude-opus-4-6
• ocp/claude-sonnet-5
• ocp/claude-sonnet-4-6 • ocp/claude-sonnet-4-6
• ocp/claude-haiku-4-5-20251001 • ocp/claude-haiku-4-5-20251001
Priority: PRIMARY (default model) Priority: PRIMARY (default model)
@@ -577,9 +577,9 @@ The simplest path: ask your AI.
What `ocp update` does: What `ocp update` does:
- **Patch bump** (e.g. `v3.14.0 → v3.14.1`): - **Patch bump** (e.g. `v3.21.0 → v3.21.1`):
light path (git pull + npm install + restart). light path (git pull + npm install + restart).
- **Cross-minor** (e.g. `v3.10 → v3.14`): - **Cross-minor** (e.g. `v3.18 → v3.22`):
full path: pre-flight check, snapshot, `setup.mjs` (with plist env-merge), full path: pre-flight check, snapshot, `setup.mjs` (with plist env-merge),
service restart, post-flight `/health` and `/v1/models` verification. service restart, post-flight `/health` and `/v1/models` verification.
- **Old version** (< v3.4.0): - **Old version** (< v3.4.0):
@@ -701,6 +701,8 @@ Your IDE → OCP (localhost:3456) → claude --output-format stream-json CLI →
OCP translates OpenAI-compatible `/v1/chat/completions` requests into `claude --output-format stream-json` CLI calls. Anthropic sees normal Claude Code usage — no API billing, no separate key needed. OCP translates OpenAI-compatible `/v1/chat/completions` requests into `claude --output-format stream-json` CLI calls. Anthropic sees normal Claude Code usage — no API billing, no separate key needed.
> **Billing-policy status (as of 2026-07).** Anthropic announced (2026-05-14) that from 2026-06-15 the `claude -p` / Agent SDK path would move to a separate metered credit pool — then **paused the change on its effective date**: *"For now, nothing has changed: Claude Agent SDK, `claude -p`, and third-party app usage still draw from your subscription's usage limits"* ([official help article](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan)). So the default path above currently bills your subscription. Anthropic has said it will give notice before any future change; if the split re-lands, OCP's opt-in [subscription-pool (TUI) mode](#subscription-pool-tui-mode) is the ready-made hedge — see the billing table there.
### Client-tools boundary ### Client-tools boundary
OCP is a **text-prompt bridge** to the official `claude` CLI. It does **not** pass through OpenAI `tools`/`functions` payloads or Anthropic `tool_use` blocks to the client. Clients (Cline, Cursor, OpenClaw, etc.) pointed at OCP receive **assistant TEXT only** — they never get `tool_calls` to execute locally. OCP is a **text-prompt bridge** to the official `claude` CLI. It does **not** pass through OpenAI `tools`/`functions` payloads or Anthropic `tool_use` blocks to the client. Clients (Cline, Cursor, OpenClaw, etc.) pointed at OCP receive **assistant TEXT only** — they never get `tool_calls` to execute locally.
@@ -709,6 +711,8 @@ Any tool use happens server-side, under the `--allowedTools` set configured on t
**Client-local tool execution is not supported by design.** Supporting it would require bypassing the `claude` CLI to call the raw Anthropic API directly — that is a different product, and is out of scope per `ALIGNMENT.md` (every OCP endpoint must correspond to something `cli.js` actually does). **Client-local tool execution is not supported by design.** Supporting it would require bypassing the `claude` CLI to call the raw Anthropic API directly — that is a different product, and is out of scope per `ALIGNMENT.md` (every OCP endpoint must correspond to something `cli.js` actually does).
**What this means for choosing OCP (workload fit).** LAN/multi-device OCP is built for **chat-class** workloads — Q&A, translation, scripting against the API, chat frontends, home-automation backends — where text in/text out is the whole job. It is **not** the right tool for a coding agent running on a *client* machine that needs the AI to read and edit *that machine's* files: tools execute on the OCP host, so the model can never touch the client's filesystem. For that workload, run `claude` (or a local OCP) directly on the machine where the code lives.
## Available Models ## Available Models
| Model ID | Notes | | Model ID | Notes |
@@ -827,11 +831,12 @@ lsof -nP -iTCP:3456 -sTCP:LISTEN
If it's an old OCP process, stop it before re-running setup: If it's an old OCP process, stop it before re-running setup:
```bash ```bash
ocp stop # if the CLI is on PATH launchctl bootout gui/$(id -u)/dev.ocp.proxy # macOS launchd
launchctl bootout gui/$(id -u)/dev.ocp.proxy # macOS launchd fallback systemctl --user stop ocp-proxy # Linux systemd (installed as a --user unit)
sudo systemctl stop ocp-proxy # Linux systemd fallback
``` ```
(There is no `ocp stop` subcommand — the proxy runs as a service, so stopping it goes through the service manager above. `ocp restart` exists for the bounce case.)
### Setup fails with "node: command not found" or version error ### Setup fails with "node: command not found" or version error
OCP requires Node.js 22.5+. Install: OCP requires Node.js 22.5+. Install:
@@ -948,6 +953,7 @@ See [Subscription-pool (TUI) mode](#subscription-pool-tui-mode) and ADR 0007 PR-
| `CLAUDE_CACHE_TTL` | `0` | Response cache TTL (ms, 0 = disabled). Set to e.g. `300000` for 5-min cache | | `CLAUDE_CACHE_TTL` | `0` | Response cache TTL (ms, 0 = disabled). Set to e.g. `300000` for 5-min cache |
| `CLAUDE_ALLOWED_TOOLS` | `Bash,Read,...,Agent` | Comma-separated tools to pre-approve | | `CLAUDE_ALLOWED_TOOLS` | `Bash,Read,...,Agent` | Comma-separated tools to pre-approve |
| `CLAUDE_SKIP_PERMISSIONS` | `false` | Bypass all permission checks | | `CLAUDE_SKIP_PERMISSIONS` | `false` | Bypass all permission checks |
| `CLAUDE_MCP_CONFIG` | *(unset)* | Path to an MCP server config JSON, passed to the spawned `claude` as `--mcp-config` (both the `-p` path and TUI `OCP_TUI_FULL_TOOLS` panes) |
| `CLAUDE_NO_CONTEXT` | `false` | Suppress CLAUDE.md and auto-memory injection (pure API mode) | | `CLAUDE_NO_CONTEXT` | `false` | Suppress CLAUDE.md and auto-memory injection (pure API mode) |
| `PROXY_API_KEY` | *(unset)* | Bearer token for shared-mode authentication | | `PROXY_API_KEY` | *(unset)* | Bearer token for shared-mode authentication |
| `PROXY_ANONYMOUS_KEY` | *(unset)* | Well-known anonymous key allowlist (multi mode). When set, this exact string bypasses `validateKey()` and grants public access. Exposed via `/health.anonymousKey` only to localhost, or to all callers when `PROXY_ADVERTISE_ANON_KEY=1`. See [Anonymous Access](#anonymous-access-optional). | | `PROXY_ANONYMOUS_KEY` | *(unset)* | Well-known anonymous key allowlist (multi mode). When set, this exact string bypasses `validateKey()` and grants public access. Exposed via `/health.anonymousKey` only to localhost, or to all callers when `PROXY_ADVERTISE_ANON_KEY=1`. See [Anonymous Access](#anonymous-access-optional). |
@@ -966,7 +972,7 @@ See [Subscription-pool (TUI) mode](#subscription-pool-tui-mode) and ADR 0007 PR-
| `OCP_TUI_STREAM_POLL_MS` | `100` | (TUI-mode, streaming) Interval at which OCP drains the delta sink. The hook fires at block granularity (seconds apart), so a finer poll buys nothing. | | `OCP_TUI_STREAM_POLL_MS` | `100` | (TUI-mode, streaming) Interval at which OCP drains the delta sink. The hook fires at block granularity (seconds apart), so a finer poll buys nothing. |
| `OCP_TUI_MAX_CONCURRENT` | `2` | (TUI-mode) Max concurrent interactive TUI turns. **Independent** of `CLAUDE_MAX_CONCURRENT` (which bounds the `-p`/stream-json path; TUI never uses it). A TUI turn is heavy (per-request cold-boot of tmux+claude + up to `CLAUDE_TUI_WALLCLOCK_MS` wallclock), so the default is low to keep small hosts (e.g. a Pi 4) alive under a burst. Excess turns **queue** (bounded); a full queue yields a 503. See ADR 0007 PR-B amendment. | | `OCP_TUI_MAX_CONCURRENT` | `2` | (TUI-mode) Max concurrent interactive TUI turns. **Independent** of `CLAUDE_MAX_CONCURRENT` (which bounds the `-p`/stream-json path; TUI never uses it). A TUI turn is heavy (per-request cold-boot of tmux+claude + up to `CLAUDE_TUI_WALLCLOCK_MS` wallclock), so the default is low to keep small hosts (e.g. a Pi 4) alive under a burst. Excess turns **queue** (bounded); a full queue yields a 503. See ADR 0007 PR-B amendment. |
| `OCP_TUI_POOL_SIZE` | `0` (off) | (TUI-mode) Number of **pre-booted warm `claude` panes** kept ready, so a request does not pay the cold boot. `0` disables the pool entirely — the request path is then exactly the cold-boot path. Max `4`; an unparseable value disables it rather than guessing. **Measured on a Mac mini (Sonnet 4.6, `--effort low`): end-to-end p50 `10.17s` (n=6, pool off) → `6.00s` (n=12 warm hits) — 4.2 s / 41%** — the pool recovers both the ~1.2 s boot *and* ~2.9 s of post-input-bar init that a pane which has been idle a moment has already finished. **Cost:** each warm pane is a *live idle `claude` process* held whether or not a request ever arrives (peak processes ≈ pool size + `OCP_TUI_MAX_CONCURRENT` + 1 booting replacement) — which is why it is opt-in. Panes are **single-use**: one turn, then killed and replaced in the background. The **first request after start (and after any model switch) is always a cold miss** — the pool warms the most recently requested model, since OCP cannot know which model the next caller wants. See `docs/plans/2026-07-13-tui-latency/`. | | `OCP_TUI_POOL_SIZE` | `0` (off) | (TUI-mode) Number of **pre-booted warm `claude` panes** kept ready, so a request does not pay the cold boot. `0` disables the pool entirely — the request path is then exactly the cold-boot path. Max `4`; an unparseable value disables it rather than guessing. **Measured on a Mac mini (Sonnet 4.6, `--effort low`): end-to-end p50 `10.17s` (n=6, pool off) → `6.00s` (n=12 warm hits) — 4.2 s / 41%** — the pool recovers both the ~1.2 s boot *and* ~2.9 s of post-input-bar init that a pane which has been idle a moment has already finished. **Cost:** each warm pane is a *live idle `claude` process* held whether or not a request ever arrives (peak processes ≈ pool size + `OCP_TUI_MAX_CONCURRENT` + 1 booting replacement) — which is why it is opt-in. Panes are **single-use**: one turn, then killed and replaced in the background. The **first request after start (and after any model switch) is always a cold miss** — the pool warms the most recently requested model, since OCP cannot know which model the next caller wants. See `docs/plans/2026-07-13-tui-latency/`. |
| `OCP_SKIP_AUTH_TEST` | *(unset)* | When `=1`, skip the `claude -p` auth probe during `setup.mjs`. After 2026-06-15 this probe draws from the Agent SDK credit pool; set this to avoid burning a metered credit on re-installs or `ocp update` runs. Auth is validated at the first real request. | | `OCP_SKIP_AUTH_TEST` | *(unset)* | When `=1`, skip the `claude -p` auth probe during `setup.mjs`. Under the announced (currently **paused**) 2026-06-15 billing split this probe would draw from the metered Agent SDK credit pool; set this to avoid burning a probe on re-installs or `ocp update` runs. Auth is validated at the first real request. |
| `OCP_TUI_FULL_TOOLS` | *(unset)* | (TUI-mode, **single-user only**) When `=1`, grant the interactive session the **same tool surface as the `-p` path** — `--allowedTools` (+ optional `--mcp-config`, read from `CLAUDE_ALLOWED_TOOLS` / `CLAUDE_MCP_CONFIG`) — instead of the default MCP-walled, built-in-tools-only set. Lets a trusted single-operator TUI deployment run a **tool-using / MCP agent** (e.g. an OpenClaw assistant) on the subscription pool. Safe because TUI **refuses to boot under `AUTH_MODE=multi`** (hard exit) — no guest key can ever reach the TUI path, so this gate cannot expose tools to an untrusted caller. (Under `AUTH_MODE=shared` + `OCP_TUI_ALLOW_LAN=1`, anyone holding the single shared key reaches it — that is the existing TUI trust model, unchanged.) Note: `--dangerously-skip-permissions` / `CLAUDE_SKIP_PERMISSIONS` is **not** supported for TUI — claude v2.1.x shows an interactive bypass-acceptance screen in headless tmux that cannot be answered, bricking the pane. Use scratch-home `settings.json` `additionalDirectories` instead. See [Subscription-pool (TUI) mode](#subscription-pool-tui-mode) and ADR 0007. | | `OCP_TUI_FULL_TOOLS` | *(unset)* | (TUI-mode, **single-user only**) When `=1`, grant the interactive session the **same tool surface as the `-p` path** — `--allowedTools` (+ optional `--mcp-config`, read from `CLAUDE_ALLOWED_TOOLS` / `CLAUDE_MCP_CONFIG`) — instead of the default MCP-walled, built-in-tools-only set. Lets a trusted single-operator TUI deployment run a **tool-using / MCP agent** (e.g. an OpenClaw assistant) on the subscription pool. Safe because TUI **refuses to boot under `AUTH_MODE=multi`** (hard exit) — no guest key can ever reach the TUI path, so this gate cannot expose tools to an untrusted caller. (Under `AUTH_MODE=shared` + `OCP_TUI_ALLOW_LAN=1`, anyone holding the single shared key reaches it — that is the existing TUI trust model, unchanged.) Note: `--dangerously-skip-permissions` / `CLAUDE_SKIP_PERMISSIONS` is **not** supported for TUI — claude v2.1.x shows an interactive bypass-acceptance screen in headless tmux that cannot be answered, bricking the pane. Use scratch-home `settings.json` `additionalDirectories` instead. See [Subscription-pool (TUI) mode](#subscription-pool-tui-mode) and ADR 0007. |
### Streaming heartbeat ### Streaming heartbeat
@@ -986,14 +992,16 @@ OCP also sends `X-Accel-Buffering: no` on SSE responses so nginx-default proxy b
### What it is and why ### What it is and why
From 2026-06-15 Anthropic routes `claude` invocations by `cc_entrypoint`: > **⚠️ Status (as of 2026-07): the billing split below is PAUSED.** Anthropic announced it for 2026-06-15, then paused it on the effective date — *"For now, nothing has changed: Claude Agent SDK, `claude -p`, and third-party app usage still draw from your subscription's usage limits"* ([official help article](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan)). While the pause holds, OCP's default `-p` path bills the subscription and **TUI-mode is a hedge, not a necessity**. The table describes the *announced* regime, kept here because Anthropic says a reworked change will return (with advance notice) — everything in this section is ready to flip on that day.
| Launch method | `cc_entrypoint` | Billing pool | The announced routing keys `claude` invocations by `cc_entrypoint`:
| Launch method | `cc_entrypoint` | Billing pool (announced regime, currently paused) |
|---------------|-----------------|-------------| |---------------|-----------------|-------------|
| `claude -p` / `--output-format` (OCP default) | `sdk-cli` | Agent SDK credit pool (~$20/mo on Pro) | | `claude -p` / `--output-format` (OCP default) | `sdk-cli` | Agent SDK credit pool (~$20/mo on Pro) |
| Interactive `claude` (no flags) | `cli` | Pro/Max subscription pool | | Interactive `claude` (no flags) | `cli` | Pro/Max subscription pool |
TUI-mode lets OCP serve requests via the interactive path so they bill against the subscription pool. The response is read from claude's native JSONL session transcript once the turn is complete, then replayed to the caller as a normal OpenAI completion or chunked SSE response. TUI-mode lets OCP serve requests via the interactive path so they bill against the subscription pool under that regime. The response is read from claude's native JSONL session transcript once the turn is complete, then replayed to the caller as a normal OpenAI completion or chunked SSE response.
### Billing-classifier labeling (`OCP_TUI_ENTRYPOINT`) ### Billing-classifier labeling (`OCP_TUI_ENTRYPOINT`)
@@ -1079,7 +1087,7 @@ a sub-5-second budget. Full measurements and methodology:
### Monitoring drift via `/health` ### Monitoring drift via `/health`
`GET /health` includes a `tui` block so you can poll for a silent billing-pool drift (the top risk after the 6/15 flip — a lost TTY flipping `cc_entrypoint` from `cli` to the metered `sdk-cli` pool would still return answers but burn metered credits). The block is **always present** (with `enabled:false` when TUI-mode is off): `GET /health` includes a `tui` block so you can poll for a silent billing-pool drift (the top risk under the announced split, if it re-lands — a lost TTY flipping `cc_entrypoint` from `cli` to `sdk-cli` would still return answers but land in the metered pool). The block is **always present** (with `enabled:false` when TUI-mode is off):
```jsonc ```jsonc
"tui": { "tui": {
@@ -1118,9 +1126,11 @@ unset CLAUDE_TUI_MODE
The stream-json path is restored immediately. No other change is needed. The stream-json path is restored immediately. No other change is needed.
### 2026-06-15 operator checklist ### Operator checklist for the (paused) billing split
Every host serving traffic must be flipped to TUI-mode **and** canary-verified before 2026-06-15, or it will bill the metered Agent SDK credit pool instead of the subscription. > **Status:** the 2026-06-15 split never took effect — Anthropic paused it on the effective date (see the status note at the top of this section). **Nothing needs flipping while the pause holds.** The checklist is retained verbatim as the runbook for if/when a reworked change lands (Anthropic has promised advance notice).
Under the announced regime, every host serving traffic must be flipped to TUI-mode **and** canary-verified before the effective date, or it will bill the metered Agent SDK credit pool instead of the subscription.
- **[Flip/rollback runbook](docs/runbooks/tui-flip-rollback.md)** — how to set `CLAUDE_TUI_MODE=true` on systemd (Linux) and launchd (macOS) hosts. Covers the `daemon-reload` requirement (systemd) and the `bootout`+`bootstrap` cycle requirement (launchd — `launchctl kickstart -k` does not reload plist env). - **[Flip/rollback runbook](docs/runbooks/tui-flip-rollback.md)** — how to set `CLAUDE_TUI_MODE=true` on systemd (Linux) and launchd (macOS) hosts. Covers the `daemon-reload` requirement (systemd) and the `bootout`+`bootstrap` cycle requirement (launchd — `launchctl kickstart -k` does not reload plist env).
- **[615-canary runbook](docs/runbooks/615-canary.md)** — after each flip, run one quiesced request and compare the Agent SDK credit balance before and after. `entrypoint:cli` in the transcript (the `cc_entrypoint` billing classifier) is necessary but not sufficient — only a stable credit balance confirms the subscription pool is being used. Balance check is a manual step (no known programmatic API for the Agent SDK credit pool balance). - **[615-canary runbook](docs/runbooks/615-canary.md)** — after each flip, run one quiesced request and compare the Agent SDK credit balance before and after. `entrypoint:cli` in the transcript (the `cc_entrypoint` billing classifier) is necessary but not sufficient — only a stable credit balance confirms the subscription pool is being used. Balance check is a manual step (no known programmatic API for the Agent SDK credit pool balance).