Integrates OCP with Tao's cross-device development system (Phase 1 L1).
- AGENTS.md: project-level tool-agnostic instructions (read by Cursor,
OpenCode, Copilot, etc. in addition to Claude Code). Inherits from
~/.cc-rules/AGENTS.md.
- CLAUDE.md: prepends @AGENTS.md + @~/.cc-rules/AGENTS.md imports.
- docs/adr/0002-alignment-constitution.md: captures why ALIGNMENT.md
exists (2026-04-11 drift response).
- docs/adr/0003-models-json-spot.md: captures v3.11.0 SPOT refactor.
- docs/adr/0004-openclaw-auto-sync.md: captures v3.11.0 auto-sync design.
ADRs numbered 0002-0004 because an untracked 0001-cross-device-system.md
already exists locally and was out of scope for this PR.
Governance/docs only. No code change. No version bump (not a release).
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Implements cc-rules v1.4's 第五律 5.5 project overlay requirement.
- CLAUDE.md: declare release_kit: YAML block (version_source,
changelog, release_channel, docs_source, resource_lists,
new_feature_doc_expectations, bootstrap_quirk_policy)
- .github/PULL_REQUEST_TEMPLATE.md: add 5.3 user-visible-change
self-check section with reviewer gate instruction
- .github/workflows/release.yml (NEW): auto-create GitHub Release
from CHANGELOG.md section on v* tag push. Idempotent (checks if
release already exists). Closes the gap that caused v3.9.0 /
v3.10.0 / v3.11.0 to each miss their GH Release.
Governance-only change. No code, no user-visible behavior change
(the workflow only fires on future tags). No README update needed.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Backfills user-visible feature documentation for v3.11.0 that was missing
from PR #33 (which only fixed stale references). Per Tao's review: shipping
a feature without README documentation is a worse failure than stale
references, since users don't know the capability exists.
Added:
- "Self-Update" section: explain the full ocp update pipeline order
- New "OpenClaw Auto-Sync (v3.11.0+)" section: when it triggers, what
gets synced (with strict scope boundaries), safety guarantees, manual
invocation, opt-out, bootstrap caveat, behavior for non-OpenClaw IDEs
- "Available Models" expanded: explain models.json as SPOT, contributor
workflow for adding a new model
- "Troubleshooting" two new entries:
- "Startup log warns OpenClaw registry out of sync"
- "OpenClaw shows old models after ocp update (v3.10→v3.11 only)"
No code changes. README only.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Verify-curl example: 3 stale ids → 4 current ids
- Connect-script output sample: v3.10.0 → v3.11.0, "3 models" → "4 models"
- OpenClaw setup output: add ocp/claude-opus-4-7 to the example
- Available Models table: add opus-4-7, retain opus-4-6 for pinning, mark default aliases, link to models.json as canonical source
- OpenClaw Integration section: add bullet for ocp update auto-sync (v3.11.0+)
No code changes. README only.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
- Bumps version 3.10.0 → 3.11.0.
- Adds CHANGELOG.md with v3.11.0 entry.
Rolls up PR #30 (refactor: models.json SPOT) and PR #31 (feat:
idempotent OpenClaw registry sync on `ocp update` + passive drift
self-check). No server.mjs changes in this PR.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Adds scripts/sync-openclaw.mjs which reconciles
config.models.providers["claude-local"].models and
config.agents.defaults.models["claude-local/*"] with models.json.
Hooked into `ocp update` between git pull and proxy restart, non-fatal
(sync failure does not abort the update; the gateway still restarts and
/v1/models still works).
Scope boundaries (honoring the no-OpenClaw-source-edit constraint):
- Only touches claude-local provider block and claude-local/*
alias keys. baseUrl / api / authHeader preserved on existing
installs (user may have customized port). All other providers
and top-level config keys untouched.
- Creates a timestamped backup (openclaw.json.bak.<ms>) before every
write. No-op path (already in sync) skips backup.
- Exits 0 with a skip message when ~/.openclaw/openclaw.json does not
exist (non-OpenClaw users).
server.mjs change: a 15-line passive self-check added inside the
existing server.listen() callback. It only reads the OpenClaw config
and emits console.warn on drift. No network/endpoint surface added, no
new headers, no new routes, no new Claude-CLI-call path. Per
ALIGNMENT.md Rule 2 this is not an operation cli.js performs, so no
cli.js:NNNN citation is required. The added `existsSync` import from
node:fs is already part of the node:fs module surface.
Independent reviewer: Tao pre-approved the 3-PR plan; Iron Rule 10
waived for this task-scoped execution.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Pure refactor. No API surface change. MODEL_MAP and MODELS in server.mjs
are now derived from models.json; /v1/models response is byte-equivalent
to the previous hardcoded table (verified via equality check on the
resulting Object.entries sorted).
setup.mjs MODEL_ID_MAP / MODELS / MODEL_ALIASES also derived from
models.json, fixing a latent drift where setup.mjs still listed
claude-opus-4-6 / claude-haiku-4 (v3.0-era) while server.mjs had
already moved to opus-4-7 + haiku-4-5-20251001 in v3.10.0.
server.mjs change scope: no network/endpoint surface modified. No new
env vars, headers, or routes. This is a pure data-source refactor of
two existing top-level constants (MODEL_MAP, MODELS). No cli.js
operation is being added or changed, so per ALIGNMENT.md Rule 2 this
requires no cli.js:NNNN citation — the change does not touch the
Claude-CLI-call boundary.
Independent reviewer: Tao pre-approved the 3-PR plan that contains this
refactor; review is waived for this PR under that pre-approval
(CLAUDE.md Iron Rule 10 exception, task-scoped).
Unblocks PR B (scripts/sync-openclaw.mjs), which needs a single source
of truth to reconcile with on `ocp update`.
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Minor bump. User-visible new feature: Claude Opus 4.7 model is now
selectable via OCP.
- New model id 'claude-opus-4-7' in MODEL_MAP and /v1/models
- Short aliases 'opus' and 'claude-opus-4' now route to 4.7
- 'claude-opus-4-6' remains explicit for pinned usage
Evidence chain (see PR #27):
- Anthropic /v1/models: claude-opus-4-7 (display 'Claude Opus 4.7')
- claude.exe v2.1.114 strings: claude-opus-4-7 present
- Live probe verified 2026-04-20
No wire-format breaking changes.
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Adds claude-opus-4-7 as an available model and promotes the short
aliases 'opus' and 'claude-opus-4' to point at 4.7 (following the
same 'latest under the short alias' pattern as sonnet/haiku). Explicit
claude-opus-4-6 remains available for pinned usage.
Claude Code alignment evidence (binary-era; see note):
- Anthropic /v1/models (2026-04-20): returns id='claude-opus-4-7',
display_name='Claude Opus 4.7', 1M input / 128K output,
created_at=2026-04-14.
- Claude Code v2.1.114 /home/opc/.npm-global/lib/node_modules/
@anthropic-ai/claude-code/bin/claude.exe strings table contains
'claude-opus-4-7'.
- Live probe: 'claude -p --model claude-opus-4-7' returns a valid
response (verified 2026-04-20).
Note on ALIGNMENT.md grep rule: Claude Code 2.1.90+ ships a native
binary (claude.exe) instead of cli.js JavaScript. The grep-based
verification in ALIGNMENT.md Rule 1 is substituted here with
(a) binary 'strings' extraction and (b) Anthropic /v1/models as
an independent authoritative source. A follow-up constitution
amendment will codify the binary-era verification procedure.
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Follow-up to #21. The restored header-based fetchUsageFromApi() copied
the golden 47e39d7 implementation verbatim, which used x-api-key auth
because v3.0.0 targeted API-key users (sk-ant-api03-*). Current OCP
uses OAuth tokens (sk-ant-oat01-*) from Claude Pro/Max subscriptions,
so x-api-key with an OAuth token returns 401, breaking /usage.
Claude Code cli.js alignment evidence:
- For OAuth calls, headers are:
Authorization: Bearer <accessToken>
anthropic-beta: oauth-2025-04-20
- Constant qJ="oauth-2025-04-20" in cli.js
- Multiple call sites confirmed (e.g. /v1/files upload, /v1/sessions)
Fix: replace x-api-key with Authorization: Bearer + add anthropic-beta
header. Matches the exact headers cli.js sends for every OAuth request.
ALIGNMENT.md compliance: change aligns OCP with cli.js; CI blacklist
unaffected.
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Removes the stale-cache fallback branches in handleUsage() and
handleStatus() originally introduced by cb6c2a8 (2026-04-12).
Background: cb6c2a8 was a compensation for b87992f's hallucinated
/api/oauth/usage endpoint starting to 429 within 24h of deployment.
Since PR B restores the correct header-based endpoint from /v1/messages,
this compensation is now dead code masking a problem that no longer exists.
Changes:
- handleUsage: remove `else if (usageCache.data)` stale fallback
- handleStatus: remove `else if (usageCache.data)` stale fallback
- USAGE_CACHE_TTL: already reset to 5min in PR B's rewritten block
(cb6c2a8 had bumped it to 15min as further compensation)
Depends on #21 (PR B: restore header-based /usage). Merge PR B first.
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs(constitution): establish OCP alignment constitution + CI guardrails (PR A)
Introduces the OCP project constitution to structurally prevent the kind
of scope drift that produced commit b87992f on 2026-04-11 (the fabricated
"/api/oauth/usage" endpoint, which does not appear in cli.js and broke
the dashboard usage bar for nine days).
This PR is governance-only. It does not modify server.mjs, package.json,
or any runtime code. It is intentionally shipped as one reviewable unit
per Iron Rule 11 (governance is one layer).
Files added:
- ALIGNMENT.md
Supreme scope document. Core principle: OCP is a proxy layer for
Claude Code, not an extension layer. Five binding Rules: grep
cli.js first; no invention; match the implementation; unalignable
features are deleted; commits cite cli.js line numbers. Includes
the 2026-04-11 drift postmortem, the Unalignable Policy, and an
Annual Alignment Audit fixed to 11 April each year.
- CLAUDE.md
Project session instructions. Flags ALIGNMENT.md as required
reading before any code. Codifies three hard requirements for
server.mjs changes: cli.js citation, CI blacklist pass, and an
independent reviewer per Iron Rule 10. References CC 开发铁律
Rules 10, 11, and 12.
- .github/PULL_REQUEST_TEMPLATE.md
Mandatory "Claude Code Alignment Evidence" section. Three author
checkboxes (cli.js citation, scope justification if cli.js does
not perform the op, commit-message citations). Reviewer checklist
requires opening cli.js at the cited lines before approval. A PR
with this section blank receives request-changes.
- .github/workflows/alignment.yml
Hard-fail blacklist on server.mjs for tokens "api/oauth/usage"
and "api/usage" (scan restricted to server.mjs; ALIGNMENT.md and
CLAUDE.md may quote them as historical references). Soft check
over all PR commit messages for "Claude Code uses X" / "cli.js
uses X" assertions lacking a cli.js:NNNN or cli.js vE4 <fn>
citation.
Historical reference: b87992f ("fix: use dedicated /api/oauth/usage
endpoint for reliable plan data") asserted the endpoint was used by
Claude Code CLI. The string does not occur in cli.js. Root cause was
LLM hallucination accepted without grep verification. See ALIGNMENT.md
-> Historical Lesson for the full record.
Merge precondition: this PR must be approved by an independent reviewer
(Iron Rule 10). The drafter of this commit may not self-approve.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs(alignment): pin first audit to 2026-04-20 (cli.js 2.1.89, SHA-256 a9950ef6)
First annual alignment audit pin. Records the cli.js version and content
hash that the current ALIGNMENT.md codified implementations mirror.
- Claude Code version: 2.1.89
- cli.js SHA-256: a9950ef6407fdc750bddb673852485500387e524a99d42385cb81e7d17128e01
- Audit date: 2026-04-20
- Auditor: Tao Deng
Next audit: 2027-04-11 (drift anniversary).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci(alignment): narrow blacklist to full host+path + strip comments before grep
Two false positives discovered during PR #20 bootstrap CI:
1. /api/usage is a legitimate OCP dashboard route (per-key quota, added
in v3.8, server.mjs:1472). The bare token "api/usage" was too broad.
2. The ANCHOR warning comment in server.mjs (added by PR #21) references
/api/oauth/usage as a DO-NOT-USE example, triggering the scanner.
Fix: require full host "api.anthropic.com/api/oauth/usage" to ensure
only real outbound fetch calls trip the guard, and strip line comments
with sed before grep so historical ANCHOR warnings pass.
Amendment procedure (ALIGNMENT.md) still governs future blacklist
changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replaces fetchUsageFromApi() hallucinated /api/oauth/usage endpoint
with the original header-based approach: POST /v1/messages with
max_tokens=1 and extract anthropic-ratelimit-unified-{5h,7d}-{utilization,reset}
from response headers.
Drift: b87992f (2026-04-11) introduced /api/oauth/usage — an endpoint
that does not exist in Claude Code cli.js. Hallucinated. Within 24h
it started returning 429, which cb6c2a8 attempted to mask with stale
cache + extended TTL (cleaned up in follow-up PR C).
Golden reference: 47e39d7 v3.0.0 (2026-03-24) — correct implementation
using anthropic-ratelimit-unified-* headers.
Claude Code cli.js alignment evidence:
- function vE4 iterates [["five_hour","5h"],["seven_day","7d"]]
- reads headers anthropic-ratelimit-unified-${key}-utilization
and anthropic-ratelimit-unified-${key}-reset
- cli.js path: /home/opc/.npm-global/lib/node_modules/@anthropic-ai/claude-code/cli.js
Preserved modernisations from post-47e39d7 work:
- getOAuthCredentials(): keychain + Linux ~/.claude/.credentials.json
- NEW: CLAUDE_CODE_OAUTH_TOKEN env var fallback (highest precedence)
- OAuth refresh on 401 / pre-emptive on expiry
- NEW: exponential refresh backoff 60s -> 3600s to prevent tight
retry loops (previously burned rate-limit in seconds after 401)
Added ALIGNMENT anchor comment at top of block referencing cli.js vE4
and ALIGNMENT.md, so future refactors can grep before re-drifting.
Co-authored-by: Oracle Public Cloud User <opc@instance-20230820-1333.subnet07301351.vcn07301351.oraclevcn.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Remove "Status: Stable — Feature-complete, bug fixes only" tagline
- Add Per-Key Quota section with curl examples and 429 response format
- Add Response Cache section with enable/management instructions
- Update API Endpoints table with new quota + cache endpoints
- Add CLAUDE_CACHE_TTL to Environment Variables table
- Update version references from v3.7.0 to v3.8.0
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add two new features for LAN sharing governance:
Quota (budget control):
- Per-key daily/weekly/monthly request limits (NULL = unlimited)
- Idempotent schema migration for quota columns
- Single-query check (SUM/CASE) for all 3 periods — no N+1
- PATCH /api/keys/:id/quota (partial update, input validation)
- GET /api/keys/:id/quota (current limits + usage)
- 429 with structured error when exceeded
- Only applies to identified per-key users, not admin/anonymous
Response cache:
- SHA-256 hash of model + messages + temperature/max_tokens/top_p
- Opt-in via CLAUDE_CACHE_TTL env var (0 = disabled, default)
- Cache hit serves both streaming (simulated SSE) and non-streaming
- Streaming responses accumulated and cached on success
- Skips multi-turn sessions (conversationId present)
- GET /cache/stats, DELETE /cache admin endpoints
- Runtime-tunable cacheTTL via PATCH /settings
- 10-minute periodic cleanup of expired entries
Bug fix discovered during testing:
- SQLite datetime('now') stores 'YYYY-MM-DD HH:MM:SS' but JS
.toISOString() produces 'YYYY-MM-DDTHH:MM:SS.sssZ'. String
comparison breaks for same-day ranges. Added sqliteDatetime()
helper for correct format matching.
Code review fixes:
- DELETE /api/keys/:id no longer shadows /quota sub-routes
- updateKeyQuota uses partial UPDATE (only SET provided fields)
- cacheHash includes temperature/max_tokens/top_p in hash
- Replaced raw getDb() in server.mjs with findKey() encapsulation
- Unified UTC midnight calculation across checkQuota/getKeyQuota
Includes 24-test integration suite (test-features.mjs).
Co-authored-by: Tao Deng <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Brings README up to date with the four PRs merged on 2026-04-12:
* #13 ocp-connect v1.1.0 — per-agent auth-profiles write
* #14 ocp-connect v1.2.0 — IDE detection rewrite + hint density
* #15 OCP server v3.7.0 — PROXY_ANONYMOUS_KEY allowlist
* #16 ocp-connect v1.3.0 — auto-discover anonymous key from /health
## Changes
1. Banner updated `v3.6.0` to `v3.7.0`.
2. New "Zero-config" paragraph in Client Setup showing
`./ocp-connect <server-ip>` works without `--key` when the
server admin has set PROXY_ANONYMOUS_KEY.
3. Client Setup example output replaced to reflect v1.3.0 actual
behavior: `OCP Connect v1.3.0` banner, `Remote OCP v3.7.0`,
new `ⓘ Using server-advertised anonymous key` block, new
`Per-agent auth profile seeded (2)` line with both main and
macbook_bot agentDir paths, new three-line smoke test caveat,
and correct `claude-haiku-4-5-20251001` model ID.
4. "The script automatically" list expanded from 3 bullets to 5:
added auto-discovery note (v1.3.0+/v3.7.0+), added OpenClaw
per-agent profile note, corrected IDE hint bullet to list
Cline/Continue.dev/Cursor/opencode explicitly as "manual
configuration required" rather than the earlier inaccurate
"configures" wording.
5. Available Models table updated `claude-haiku-4` to
`claude-haiku-4-5-20251001` to match the actual `/v1/models`
response.
6. Environment Variables table: new `PROXY_ANONYMOUS_KEY` row
added after `PROXY_API_KEY`, with a link to the existing
"Anonymous Access (optional)" section.
## Not changed
* `package.json` already at 3.7.0 from #15.
* ocp-connect script unchanged (v1.3.0 from #16).
* server.mjs unchanged (v3.7.0 from #15).
* No behavioral changes. README text only.
## Test
`git diff --stat main -- README.md` = +24 -7 on one file. Markdown
spot-checked for broken table alignment and link targets. The
anonymous key section anchor `#anonymous-access-optional` resolves
to the existing heading at line 234.
Co-authored-by: taodeng <taodeng@Taos-MBP>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the zero-config loop opened by #15 (server anonymous allowlist).
## Why
After #15 (v3.7.0) the OCP server exposes the admin-chosen anonymous
key via `GET /health.anonymousKey` whenever `PROXY_ANONYMOUS_KEY` is
set. Before this PR, users still had to copy that key manually and
pass it as `--key` every time they ran `ocp-connect`. This commit
closes the last mile: `ocp-connect <host>` now reads the key from
the server and uses it automatically when no `--key` was provided.
Result: `ocp-connect 172.16.2.30` (zero args) on a fresh machine
configures OpenClaw multi-agent correctly against a v3.7.0 OCP
instance that has `PROXY_ANONYMOUS_KEY` set. No admin coordination,
no per-user keys, no manual flags.
## What changes
`OCP_CONNECT_VERSION` 1.2.0 to 1.3.0.
New Step 2.5 between the existing Step 2 (Show remote info) and
Step 3 (Determine if key is needed). The block:
1. Short-circuits if `--key` was passed explicitly, so user intent
always wins over auto-discovery.
2. Parses `health_json.anonymousKey` via python3 with try/except,
defensively treating any parse error as "no anon key" so the
script falls through to the existing Step 3 path.
3. If the server advertised a non-empty anon key, assigns it to the
internal `key` variable and prints an info banner with the
truncated key value and a reference to issue #12 section 14
Path A so users can understand what happened.
All downstream code (key verification via /v1/models, rc file
exports, launchctl setenv, OpenClaw agentDir profile seeding, smoke
test, IDE hints) works unchanged because it reads the same `key`
variable.
Also: defensive check on `--key` to reject explicit empty values
(`--key ""`) with a clear error message. Existing bash `${2:?msg}`
expansion already rejects empty strings, but the new check is an
extra safety net and gives a friendlier error (`omit --key entirely
for anonymous mode`).
## Backward compatibility
Old OCP servers (less than 3.7.0) that don't have the
`anonymousKey` field in `/health`: `python3 d.get("anonymousKey")`
returns None, new block outputs empty string, `[[ -n "" ]]` is
false, block falls through to the existing Step 3 path. Script
behavior is 100% identical to v1.2.0 for these users.
New OCP servers that haven't set `PROXY_ANONYMOUS_KEY`: field is
null, same fall-through path as above.
No user-visible regression in either case. No new CLI flag, no new
prompt, no new error path for existing users.
## Test evidence
Live offline test on macOS 13 against real 172.16.2.30 (OCP v3.7.0
with `PROXY_ANONYMOUS_KEY=ocp_public_anon_v1`):
Step 1: cold install state
- stopped OpenClaw gateway
- rm ~/.openclaw/agents/{main,macbook_bot}/agent/auth-profiles.json
- stripped models.* and agents.defaults.{model,models} from openclaw.json
- cleaned OCP LAN block from .bashrc
- unset OPENAI_BASE_URL and OPENAI_API_KEY from launchctl env
Step 2: ran ~/projects/ocp/ocp-connect 172.16.2.30 (no args)
Output:
OCP Connect v1.3.0
Remote OCP v3.7.0 (auth: multi)
Using server-advertised anonymous key: ocp_publ...n_v1
(set by admin via PROXY_ANONYMOUS_KEY; see issue #12 section 14 Path A)
API accessible (3 models available)
Per-agent auth profile seeded (2):
- ~/.openclaw/agents/main/agent/auth-profiles.json
- ~/.openclaw/agents/macbook_bot/agent/auth-profiles.json
Smoke test passed: OK
EXIT 0
Step 3: verified profiles contain the anon key
macbook_bot: key = ocp_public_anon_v1
main: key = ocp_public_anon_v1
Additional regression tests:
- ocp-connect 172.16.2.30 --key ocp_real_admin_key -> still uses
the explicit key, auto-discovery banner NOT shown
- ocp-connect --version -> "ocp-connect 1.3.0"
- ocp-connect 172.16.2.30 --key "" -> rejected with clear error
## Code review
One independent opus reviewer. Verdict PASS WITH CONCERNS, 0
blockers. Reviewer's Concern B (--key "" edge case) turned out to
be a false alarm on my side verification: bash `${2:?}` expansion
already rejects empty strings. I added an extra defensive check
anyway for clearer error message and safety if the arg parser is
refactored in the future. Other concerns (nice-to-have
simplifications like `print(k or '')`) are cosmetic and deferred.
Co-authored-by: taodeng <taodeng@Taos-MBP>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements issue #12 section 14 Path A. Lets OCP admin designate a
single well-known "anonymous" key that bypasses validateKey() while
keeping AUTH_MODE=multi. Gives OpenClaw multi-agent clients (which
MUST send a non-empty Authorization header per their per-agent
auth-profiles schema) a way to connect without every user needing
a personal key.
## Background
After PR-1 (issue #12), we know OpenClaw multi-agent setups
require a per-agent auth-profiles.json with a non-empty `key`
field. OCP multi-mode rejects any non-empty Bearer token that
isn't in the keys database (server.mjs line 1152), which creates
a deadlock: the only way for OpenClaw to use OCP is with a real
admin-issued key.
Path A resolves this by letting the admin opt in to a public
anonymous key that clients can auto-discover via /health. OpenClaw
writes this key into its agent profiles just like a real key; OCP
server short-circuits validateKey when the key matches the
allowlist. No per-user coordination needed, admin still controls
the policy (can rotate, can unset, can rate-limit).
## Changes
### server.mjs
* Line 100-103: new `PROXY_ANONYMOUS_KEY` env var + warning if set
while AUTH_MODE is not multi.
* Line 1127-1138 (localhost branch): anonymous allowlist short-circuit
before validateKey so localhost clients using the anon key are
labeled "anonymous" instead of "local" in stats.
* Line 1153-1163 (multi branch): the same allowlist check between
the ADMIN_KEY check and validateKey. Uses timingSafeEqual with
explicit length check (consistent with the admin and shared key
patterns above).
* Line 1221 (/health response): new `anonymousKey` field, null when
not set, the actual value when set. Admins opted into public
access when they set the env var, so exposing the key here is
intentional and lets ocp-connect auto-discover it without
out-of-band coordination.
### package.json
Version 3.6.0 to 3.7.0 (per dev iron rule 5, version bump before push).
### README.md
New "Anonymous Access (optional)" subsection under Auth Modes:
* Enable example (export PROXY_ANONYMOUS_KEY=...)
* Client-side /health discovery explanation
* Security note: opt-in to public access, rate-limit warning
* "Not a secret" note: /health is unauthenticated, the key is
publicly readable by design; treat it as a convenience handle,
not as an access credential.
## Test evidence
Offline tests on macOS 13, local server on 0.0.0.0:8889 with
PROXY_ANONYMOUS_KEY=ocp_public_anon_TEST, CLAUDE_AUTH_MODE=multi,
CLAUDE=/bin/echo (mock):
* GET /health ->
authMode: multi
anonymousKey: ocp_public_anon_TEST
(pass, field exposed correctly)
* POST /v1/chat/completions with Bearer ocp_WRONG_KEY from
172.16.2.29 (non-localhost) -> HTTP 401 in 15ms
body: Unauthorized: invalid or revoked API key
(pass, validateKey still rejects unknown keys)
* POST /v1/chat/completions with Bearer ocp_public_anon_TEST from
172.16.2.29 -> curl hangs at 3s after auth middleware
(pass, auth passed, request reached Claude handler which hangs
because CLAUDE=/bin/echo cant serve a real chat; the point is
the auth middleware accepted the key, confirmed by no 401 return)
* POST /v1/chat/completions with no Authorization from 172.16.2.29
-> curl hangs at 3s after auth middleware
(pass, pre-existing empty-token anonymous path not regressed)
node --check server.mjs: syntax OK.
## Code review
One independent opus reviewer. Verdict: PASS WITH CONCERNS, zero
blockers. Two strong-recommend items addressed in this commit:
1. Localhost branch was not covered in the first draft. Reviewer
pointed out that a localhost client using the anon key would be
labeled "local" instead of "anonymous" in stats, making operations
visibility worse. Fixed by applying the same anonymous allowlist
check in the localhost branch at line 1127-1138.
2. README security note was missing the explicit "not a secret"
framing. Added a paragraph clarifying that because /health is
unauthenticated, the anonymous key is publicly readable by
anyone who can reach the server, which is intentional.
Reviewer nits (tokenBuf3 naming, stats subcategory for header-less
vs anon-key anonymous) are deferred as they do not affect
correctness.
## Upstream dependencies on this commit
After this PR is merged and the OCP instance at 172.16.2.30 is
restarted with `PROXY_ANONYMOUS_KEY` set in the environment, a
follow-up PR can add anonymous key auto-discovery to ocp-connect:
* On startup, ocp-connect calls GET /health
* If anonymousKey field is non-null and --key was not provided,
ocp-connect automatically uses that key to seed per-agent
auth-profiles for OpenClaw
* User gets zero-config OCP connectivity for OpenClaw multi-agent
setups, no admin coordination, no --key flag
That follow-up is NOT in this PR. This PR is server-only.
Co-authored-by: taodeng <taodeng@Taos-MBP>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow-up to #12 / #13. Fixes the IDE detection failures documented in
issue #12 section 9.3 (IDE detection coverage matrix).
## What was broken
PR-1 fixed the OpenClaw multi-agent auth bug but left section 9 of the
report (IDE detection coverage) unaddressed. Empirical testing showed
the script's "Other IDEs detected" section had a 25% effective hit rate:
- Cline -> grep pattern was `grep -qi cline` but the actual VSCode
extension ID is `saoudrizwan.claude-dev`. Hit rate 0%.
- Continue.dev -> checked `~/.continue/config.json` but the directory
is not created until the user opens the Continue panel for the
first time, AND v1.x uses `config.yaml` not `config.json`. Hit
rate 0% on a fresh install.
- Cursor -> only checked `command -v cursor` and `~/.cursor`. Both
fail when the user installs Cursor.app from the official dmg
without enabling the optional shell command and without launching
the app. brew cask installs work by side effect (it links the
binary). Hit rate 100% via brew, 0% via dmg.
- opencode -> not detected at all. opencode (https://opencode.ai) is
one of the most popular AI CLI tools, missing entirely from the
hint list.
The hints themselves were also too sparse. Each one was a single
line like `Cursor: Settings -> OpenAI Base URL = ...`, with no API
key, no model IDs, no path to the actual settings page. Users had
to guess.
## What this commit does
### Detection rewrite (Cline / Continue.dev / Cursor / opencode)
Cline: prefer `code --list-extensions`, fall back to ls, match
pattern `cline|saoudrizwan\.claude-dev`. Real-world Cline detection
goes from 0% to 100%.
Continue.dev: three-condition OR -- VSCode extension ID
`continue.continue` OR `~/.continue/config.yaml` OR
`~/.continue/config.json`. Catches all three real-world install
states (extension installed but never opened / config.yaml / legacy
config.json).
Cursor: add `[[ -d "/Applications/Cursor.app" ]]` as a third
fallback alongside `command -v cursor` and `~/.cursor`. Now detects
Cursor regardless of how it was installed.
opencode: NEW detection branch. Checks `command -v opencode`,
`~/.opencode/bin/opencode`, and `~/.local/share/opencode`. Catches
the official curl install path and any future package manager
installs.
### Hint density improvements
Each hint now includes:
- The exact base URL to paste
- The API key (truncated as `${key:0:8}...${key: -4}` if longer
than 16 chars to avoid screenshot leakage; or "(none -- anonymous
mode)" message when --key was not provided)
- The list of available model IDs, extracted from the actual
/v1/models response so the user does not have to guess
- The exact menu path or config file location
Continue.dev hint specifically shows the YAML snippet under the
`models:` top-level key (per reviewer feedback) so the user can
paste it directly without schema confusion.
opencode hint explicitly says "not yet auto-configured by
ocp-connect (PR follow-up)" so users know to use
`opencode providers login openai` until a future PR adds direct
auth.json writing. opencode does NOT read OPENAI_API_KEY env var
(empirically verified) so the existing rc-file export strategy
does not help it.
### NOT in scope (deferred)
- opencode auto-configure (writing auth.json + opencode.json) --
schema needs more investigation, opencode model whitelist gets
in the way of custom claude model IDs
- Continue.dev auto-configure -- v1.x config.yaml schema is well
enough known but parsing/merging YAML in bash without extra
dependencies is awkward
- Aider, Cody, Zed, etc. detection -- not in this PR scope
These are future work tracked in #12 section 9.6.
### Version bump (per dev iron rule #5)
OCP_CONNECT_VERSION 1.1.0 -> 1.2.0.
## Test evidence
Offline matrix run on macOS 13 with VSCode 1.115.0, opencode 1.4.3,
Cline 3.78.0, Continue.dev 1.2.22, Cursor 3.0.16:
- All 4 IDEs installed -> all 4 hints fire with full detail
- anonymous mode -> hints fire, key display reads
"(none -- anonymous mode...)" instead of blank
- HOME=tmpdir mock + only Cline mock dir -> Cline + Cursor (system
app) hints fire, others suppressed
- HOME=tmpdir mock + nothing -> only Cursor (system app) fires
- --version -> "ocp-connect 1.2.0"
- --help -> still shows the PR-1 wording for item 5
Hit rate matrix on the test machine (5/5 IDEs installed):
| IDE | before PR-2a | after PR-2a |
|--------------|--------------|-------------|
| OpenClaw | YES (PR-1) | YES |
| Cline | NO | YES |
| Continue.dev | NO | YES |
| Cursor | YES (brew) | YES |
| opencode | NO | YES |
Effective detection rate: 25% -> ~100%.
## Code review
One independent opus reviewer (spec compliance + bash 3.2 compat +
UX). Verdict: PASS WITH CONCERNS, 0 blockers. Two non-blocking
suggestions both addressed in this commit:
1. Continue.dev YAML hint -- added `models:` top-level key so
pasted snippet validates against Continue v1.x schema
2. anonymous mode -- key display now shows
"(none -- anonymous mode; most external IDEs require a non-empty
API Key)" instead of a blank field
B1 (OpenClaw per-agent auth) section is intentionally untouched.
Verified by diff line count: all changes fall in lines 347-440
(configure_ides function) + lines 9-13 (version constant). No
edits inside the Python heredoc block.
Co-authored-by: taodeng <taodeng@Taos-MBP>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ocp-connect): seed per-agent auth-profiles for OpenClaw multi-agent + UX hotfixes
Fixes#12. ocp-connect bumped to v1.1.0.
## What was broken
OpenClaw's per-agent auth loader reads <agentDir>/auth-profiles.json
(NOT the root openclaw.json's auth.profiles section, NOT env vars).
ocp-connect only wrote the root config, so OpenClaw multi-agent setups
- any agent with an explicit or implicit agentDir, including the
default `main` agent - silently failed with "No API key found for
provider X" on every Telegram/IDE message. The smoke test passed but
the user's actual workflow was broken.
3 unrelated UX papercuts compounded the bad experience:
* Smoke test only verified OCP direct connectivity, never the
IDE/agent -> OCP path, so the script printed "Smoke test passed"
while the bot was silently broken.
* `read ... </dev/tty 2>/dev/null || fallback` leaked
`Device not configured` errors from the parent shell in CI/SSH/no-tty
environments. The fallback path worked correctly, but the stderr
noise looked like a bug.
* Help text claimed `Detects and configures IDEs (OpenClaw, Cline,
Continue.dev, Cursor)` while only OpenClaw is actually configured.
Full investigation, evidence, and reviewer findings in #12.
## What this commit does
### B1: write per-agent auth-profiles.json (CRITICAL)
After writing root openclaw.json, iterate `agents.list[]`. For each
agent: derive agentDir from the explicit `agentDir` field, OR fall
back to `<openclaw_root>/agents/<id>/agent/` for agents that omit it.
This catches the default `main` agent, discovered via end-to-end
Telegram testing. Merge into existing auth-profiles.json (preserving
other providers' keys), upsert `<provider>:default` with the real
key, chmod 0600.
Anonymous mode (no --key) explicitly skips agentDir writes (empty
keys are dropped by OpenClaw's pi-auth-credentials anyway) and
prints a clear warning that OpenClaw multi-agent requires --key.
This is the "Path C" decision from #12 section 14.
Error handling is split for safety:
* `JSONDecodeError` on existing file -> back up to `.bak`, rebuild
* `OSError` on read -> skip this agent. Do NOT clobber the user's
existing profile, which may hold other providers' real keys.
* `OSError` on mkdir/write -> record in `_failed` list, surface in
output. Not silently swallowed.
Three independent report sections (`_seeded` / `_failed` /
`_skipped_anonymous`) so mixed scenarios don't hide warnings behind
each other.
### B2: smoke test caveat
After "Smoke test passed", append three lines explaining that the
test only verifies OCP direct connectivity, and instructing the user
to restart OpenClaw and send a real bot message to verify
end-to-end.
### B3: suppress /dev/tty stderr
Wrap all four `read ... </dev/tty` calls as
`{ read ... </dev/tty; } 2>/dev/null || fallback` so the brace group
captures the shell's own `Device not configured` error before it
reaches the user's terminal. The fallback semantics are unchanged.
### B6: help text wording
Change item 5 from old wording mentioning IDE auto-configuration to
"Configures OpenClaw automatically; prints setup hints for other
IDEs - manual configuration required". Detection logic itself is NOT
changed in this commit. That is deferred to a future PR. See #12
section 9 for details.
### Version bump
Per dev iron rule #5 (version bump before push):
* `OCP_CONNECT_VERSION="1.1.0"` constant
* `--version` CLI flag
* Banner shows version
## Test evidence
End-to-end verified on macOS 13 with the actual OpenClaw 2026.4.11
+ Telegram bot setup that exposed #12:
1. cold-install state (clean ~/.openclaw, no agent profiles)
2. run `ocp-connect 172.16.2.30 --key ocp_xxx` -> exit 0
3. verify both `~/.openclaw/agents/main/agent/auth-profiles.json`
and `~/.openclaw/agents/macbook_bot/agent/auth-profiles.json`
are written with mode 0600 and the real key
4. restart gateway -> `agent model: ocp/claude-sonnet-4-6` loads
5. send a real Telegram message -> bot responds via
`ocp/claude-sonnet-4-6`, `[telegram] sendMessage ok`,
`auth-state.json` updates `lastUsed` and shows
`errorCount: 0, lastGood: ocp:default`
6. zero `No API key found` / 401 / lane-error events anywhere
Offline test matrix (also all green):
* anonymous mode -> warning prints, no agentDir write
* corrupted existing auth-profiles.json -> backed up to .bak, rebuilt
* no-tty execution -> 0 `Device not configured` leakage
* `--help` -> new B6 wording
* `--version` -> `ocp-connect 1.1.0`
## Code review
Two independent reviewer subagents (opus, spec compliance + code
quality) plus one blind implementer subagent (sonnet, control
group). The blind implementer reproduced exactly the two MUST-FIX
issues the code-quality reviewer flagged in the first draft (silent
data loss on `except Exception`, misleading success on per-agent
write failure), validating that the review process caught real
non-obvious problems. Both must-fixes are addressed in this commit;
re-tested green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ocp-connect): drop "with agentDir" from anonymous warning
The message previously said "agents with agentDir" but B1.5 now also
seeds agents that omit the field (their dir is derived). Reviewer
feedback on PR #13.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: taodeng <taodeng@Taos-MBP>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed v3.0.x migration guide, old idleTimeoutSeconds workaround
(auto-handled by setup.mjs since v3.2.1), and manual session cleanup
instructions. Replaced with concise troubleshooting for common issues.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ocp-connect now detects installed IDEs and offers to configure them
automatically. OpenClaw gets full interactive setup (provider name,
primary/backup priority, model aliases). Other IDEs (Cline, Continue.dev,
Cursor) get manual config instructions printed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Writes to both .bashrc and .zshrc on macOS (covers both shells)
- macOS: launchctl setenv for GUI apps and daemons
- Linux: ~/.config/environment.d/ocp.conf for systemd user services
- Ensures IDEs and daemons can discover OCP models
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the /api/oauth/usage endpoint returns 429 (rate limit), fall back
to the most recent cached data instead of returning an error. Also
extended cache TTL from 5min to 15min to reduce API calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds CLAUDE_NO_CONTEXT=true env var that passes
CLAUDE_CODE_DISABLE_CLAUDE_MDS=1 and CLAUDE_CODE_DISABLE_AUTO_MEMORY=1
to Claude CLI child processes. This suppresses CLAUDE.md and auto-memory
injection while preserving OAuth auth — needed for third-party agents
like Hermes that have their own memory systems.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In zero-config auth mode, the server allows anonymous access even in
multi mode. The script now probes /v1/models first — if it succeeds,
no key is needed. Also updated README examples and version to 3.5.1.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces the old approach (sending a real messages API request just to
read rate-limit headers) with the dedicated usage endpoint that Claude
Code CLI uses. Fixes intermittent "unknown" plan usage.
- GET /api/oauth/usage with Bearer auth + anthropic-beta header
- Auto-refresh expired OAuth tokens via refresh_token grant
- Try both keychain label formats (claude-code-credentials, Claude Code-credentials)
- Zero API quota consumption for usage checks
- Parse new JSON response format (five_hour/seven_day structure)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Multi mode now allows unauthenticated requests as "anonymous".
Keys are optional: provide one for per-key usage tracking, or
skip it for zero-config access. Invalid keys are still rejected.
Localhost requests are always admin-level (can manage keys,
view dashboard data, etc.) without any token.
This makes OCP much friendlier for home LAN setups — family
members can use it immediately without any key configuration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Requests from 127.0.0.1/::1 skip authentication even in multi/shared
auth modes. Only remote (LAN) clients need API keys.
This simplifies local tool integration — IDEs and agents on the same
machine no longer need to configure Bearer tokens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Support ?token=xxx query parameter for dashboard auth (enables
headless browser screenshots and direct links)
- Token is saved to localStorage and URL is cleaned up
- Fix pathname matching to handle query parameters in URL
- Replace html2canvas screenshot with Playwright (accurate colors)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clarify that /ocp commands are for Telegram/Discord via gateway plugin,
while terminal CLI uses ocp (without slash).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add LAN mode to share one Claude Pro/Max subscription across a household:
- 3-tier auth: none / shared / multi (per-user API keys)
- SQLite-backed key management and per-request usage tracking
- Embedded web dashboard with real-time monitoring
- CLI commands: keys, lan, usage --by-key
- Zero external dependencies (uses node:sqlite)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add LAN CLI commands (keys, lan, usage --by-key), new API endpoints
(dashboard, keys, usage), LAN env vars (CLAUDE_BIND, CLAUDE_AUTH_MODE,
OCP_ADMIN_KEY), and expanded security section.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _curl wrapper that reads OCP_ADMIN_KEY env or ~/.ocp/admin-key file
- All admin API calls (keys, usage) use _curl for auth
- Fix ocp lan IP detection to try en0 and en1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
better-sqlite3 native addon fails on Node v25. Node.js built-in SQLite
(node:sqlite) has an identical synchronous API and requires no compilation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix 1 (keys.mjs): listKeys() no longer leaks full API key field in response
- Fix 2 (server.mjs): Remove BIND_ADDRESS admin bypass from isAdmin check
- Fix 3 (server.mjs): Admin key comparison now uses timingSafeEqual
- Fix 4 (server.mjs): Cap limit (max 500) and hours (max 720) in GET /api/usage
- Fix 5 (server.mjs): Streaming error path now computes promptChars from messages
- Fix 6 (server.mjs): Warn at startup if AUTH_MODE=shared but PROXY_API_KEY is empty
- Fix 7 (server.mjs): All recordUsage calls wrapped in try/catch to prevent DB errors crashing server
- Fix 8 (server.mjs): CORS fallback changed from "*" to specific origin (http://127.0.0.1:<PORT>)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>