mirror of
https://github.com/dtzp555-max/ocp.git
synced 2026-07-21 21:15:09 +00:00
c12013a09419c84eadcc96e8e9177012bb807c30
151
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c12013a094 |
feat(upgrade): add scripts/upgrade.mjs + scripts/lib/snapshot.mjs
Implements the upgrade dispatcher (noop / dry-run / light delegation / full path) and the snapshot writer/reader/list module. Full path snapshots plist + db + admin-key + openclaw.json before mutating, runs the 6 phases (pre-flight, snapshot, fetch+install, reconfigure, restart, post-flight), and emits a heads-up before launchctl bootout per notify_before_prod_service_restart.md policy. mockExec/mockDoctor injection points let tests verify the phase ordering without touching the real shell. fresh_install + rollback paths are deferred to Bundle 3. No cli.js citation needed: this is OCP-internal upgrade tooling with no corresponding cli.js operation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c4e5374046 |
fix(doctor): handle unparseable version + empty health body
Three issues raised by code-quality reviewer on
|
||
|
|
414b473eb7 |
feat(ocp): wire cmd_doctor into bash CLI; dispatch to scripts/doctor.mjs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
b65201b395 |
feat(doctor): add ocp doctor with --json + next_action contract
Implements scripts/doctor.mjs with semver-aware path selection (noop/update/upgrade/fresh_install/fix_oauth/fix_service) and the JSON contract documented in the design spec. Service health + OAuth checks integrated; mockable via opts.mockHealth for unit tests. 8 unit tests cover the kind dispatch tree and the next_action shape for each kind. No cli.js citation needed: this is OCP-internal tooling with no corresponding cli.js operation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
55c576bbb1 |
fix(setup): preserve user-customised plist/systemd env vars on re-setup (#90)
* fix(setup): merge plist/systemd env vars instead of overwriting
setup.mjs previously wrote the launchd plist and the Linux systemd unit
with writeFileSync(path, NEW_TEMPLATE_STRING), which silently dropped any
user-customised env vars (CLAUDE_HEARTBEAT_INTERVAL, CLAUDE_CACHE_TTL,
etc.) on every re-setup or upgrade. This change introduces
scripts/lib/plist-merge.mjs which preserves keys present only in the
existing file and lets the template's known keys win. Linux variant uses
the same logic against Environment=KEY=VALUE lines.
Tests added in test-features.mjs cover preserve / override / first-install
for both formats.
No cli.js citation needed: this is OCP-internal installer behaviour with
no corresponding cli.js operation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(setup): plist-merge code-quality follow-up
Three issues raised by the code-quality reviewer on
|
||
|
|
750b25ba77 |
chore(release): v3.14.0 — security hardening (sessions namespacing + file modes + /api/usage scope) (#89)
Bump version 3.13.0 → 3.14.0. No functional code change in this PR; all three security fixes are already merged in main via PRs #86, #87, #88. This PR covers only metadata + docs: - package.json: version bump to 3.14.0 - CHANGELOG.md: v3.14.0 entry with Features / Behavior changes / Verification / Governance sections - README.md: /api/usage self-scope note in Auth Modes §, API Endpoints table row update, file-mode bullet in Important Notes § cli.js citation N/A: this release PR modifies no server.mjs / setup.mjs / keys.mjs. The three underlying security PRs (#86, #87, #88) each carry their own cli.js-citation-not-applicable disclaimer per PR #75 pattern, as they are OCP-internal access-control, session-state, and file-permission changes with no corresponding cli.js operation. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>v3.14.0 |
||
|
|
fd6e875bd7 |
fix(security): /api/usage default scope = self; admin all-keys requires ?all=true (#88)
Identified during privacy/security audit (.claude/research/ocp-security-audit.md
§3 "/api/usage 全量泄露所有 key 时序"). Previously, any caller authenticated as
admin received the full per-key usage byKey + recent + timeline block, which
means a brief admin-token compromise exposes every household member's request
volume, timing, and which-model — metadata, but sensitive metadata.
This is a deliberate breaking change for least-privilege.
Behavior matrix (post-change):
Caller | Default scope | ?all=true
----------------------------------------|-------------------|---------------------
anonymous (PROXY_ANONYMOUS_KEY) | own ("anonymous") | ignored (still own)
authenticated non-admin key | own (key.name) | ignored (still own)
admin (no flag) | own ("admin") | n/a
admin with ?all=true | n/a | full byKey/recent
localhost-no-token / "local" | own ("local") | full (isAdmin=true)
Response shape is unchanged except for an additional advisory `scope` object
(`{self, all}`); existing fields keep their structure so dashboards and scripts
continue to parse cleanly — they just see less data unless they opt in.
Audit-friendly addition: when admin opts into `?all=true`, the server now
emits `logEvent("info", "admin_usage_full_scope", { caller, ip })` so every
privilege-escalation moment is recorded.
Backward-compat warning for admin scripts:
existing cron jobs / scripts that call `/api/usage` to inventory all keys must
add `?all=true` after this change. Default-self is the new safe behavior.
Dashboard (dashboard.html): adds a "Show all keys" checkbox in the Usage by
Key section. Hidden by default; revealed only when refreshKeys() succeeds
(same admin gate as the keys-management section). Toggle state persists in
localStorage as `ocp_usage_show_all`.
cli.js citation
---------------
This is OCP-internal admin API behavior — there is no `cli.js` operation to
cite. ALIGNMENT.md Rule 2 (the rule that limits OCP to operations that exist
in `cli.js`) does not constrain access-control rules for the OCP server's
own admin endpoints.
Smoke test (isolated test server on :3489, prod :3478 untouched):
- node --check server.mjs SYNTAX_OK
- npm test 43/43 passed
- alignment.yml blacklist grep BLACKLIST_CLEAR
- LAN scope matrix alice/bob own only; alice ?all=true denied;
anon ?all=true denied; admin all=true full +
audit log emitted
Iron Rule 10
------------
Author cannot self-approve. A separate fresh-context reviewer must read the
diff and confirm the cli.js-not-applicable statement is correct before merge.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
8c0b97f3ae |
fix(security): tighten on-disk credential file modes (700/600) (#87)
Credential-bearing OCP artifacts were created at default umask (0644/0755),
making them world-readable on multi-user hosts. This commit hardens them at
writer time and adds an idempotent startup reconciliation so existing prod
installs are fixed automatically on next service restart.
Changes:
- keys.mjs: mkdirSync(OCP_DIR, { mode: 0o700 }) + chmodSync after to handle
pre-existing dirs; chmodSync(DB_PATH, 0o600) after first getDb() open.
- setup.mjs: chmodSync(plistPath, 0o600) after writeFileSync on macOS;
chmodSync(unitPath, 0o600) after writeFileSync on Linux.
- server.mjs: _tightenFileModesIfPossible() reconciliation block — idempotently
chmods ~/.ocp (700), ~/.ocp/admin-key (600), ~/.ocp/ocp.db (600) on startup;
emits a single info-level log line when any file is tightened; ignores ENOENT
and wraps EPERM in a warn log so startup is never crashed by chmod failure.
Backward compat: all files remain accessible to the same-user owner; 0o600
is still fully readable/writable by the process. Existing prod boxes with
old 0644 ~/.ocp directories get fixed-up on next launchd/systemd restart
without any manual intervention.
cli.js citation: this change is OCP-internal file-permission hardening only.
No cli.js function corresponds to chmod or credential-file management. This
is an OCP-local security improvement that is out of scope for the cli.js
citation requirement per ALIGNMENT.md Rule 2; the cli.js boundary applies
to proxy protocol and API surface changes, not host-filesystem hardening.
Identified during privacy/security audit — .claude/research/ocp-security-audit.md.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
68acf15373 |
fix(security): namespace sessions Map by keyId — close cross-key conversation collision (#86)
**Bug**: the sessions Map used the raw client-supplied conversationId string as
its key. Two callers with different API keys (or one anonymous + one
authenticated) using the same session_id="default" collided in the Map,
sharing a cli.js subprocess and conversation history — a cross-tenant context leak.
**Fix**: introduce _sessionKey(conversationId, keyName) → "${keyName}|${conversationId}".
Replace every sessions Map site (has / set / get / delete) with the namespaced key.
keyName comes from req._authKeyName (set by auth middleware). Anonymous callers
produce "anon|<id>"; admin produces "admin|<id>"; per-key callers produce
"<keyName>|<id>" — matching the convention used by cacheHash() for per-key cache
isolation (D1, v3.13.0).
**cli.js citation — not applicable**: This PR changes OCP-internal session lifecycle
bookkeeping (the sessions Map that OCP maintains to thread --resume flags across
requests). There is no corresponding cli.js operation to cite. ALIGNMENT.md Rule 2
(limiting OCP to operations cli.js performs) does not constrain in-process state
representation. Same pattern as #75.
**Scope of changes (server.mjs only)**:
- New helper: _sessionKey() — 3 lines after sessions Map declaration
- spawnClaudeProcess(): accepts keyName param; all 4 sessions Map calls → _sessionKey
- handleSessionFailure(): uses sessionKey (not raw conversationId) for sessions.delete
- callClaude(): accepts and forwards keyName to spawnClaudeProcess
- callClaudeStreaming(): forwards authInfo.keyName to spawnClaudeProcess
- Request handler: both callClaude() call sites pass req._authKeyName
- Cleanup interval + GET /health + GET /sessions: strip "keyName|" prefix before log/display
**Smoke test**:
- node --check server.mjs → SYNTAX OK
- npm test → 43/43 passed, 0 failed
- Hand-traced has/set/get/delete paths: cross-key collision absent ✓
Identified during privacy/security positioning brainstorm — `.claude/research/ocp-security-audit.md`
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
a71c939bf8 |
docs(readme): remove zhihu blog backlink + badge (article was removed) (#85)
The Chinese blog post linked from these two places was taken down by the 知乎 platform shortly after publication. Both pointers now resolve to a "content removed" warning page, which is a worse signal to visitors than no link at all. Removing both before they reach more README readers. Reverts the additions from #81 specifically: - Top badges row: drop the "blog · engineering story" shield - §Why OCP? closing blockquote: drop the line pointing at the article The 知乎 article URL is no longer reachable, so retaining the references would route incoming traffic to a dead page that suggests the project is itself problematic. Better to leave that section silent until a working canonical write-up exists somewhere. Doc-only change. server.mjs not touched. ALIGNMENT.md Rule 5 (cli.js citation) does not apply. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d245c62df7 |
docs(images): replace dashboard.png with multi-client + multi-session traffic (#84)
Continues #82 / #83 — those captures had stats counters at 0 / 3 respectively, with 0 active sessions. The Sessions card therefore visually undermined the rest of the dashboard. Re-captured after deliberately seeding LAN-side traffic from both Pi231 and MacBook clients (each holding a per-key API token issued on the Mac mini server), mixing single-turn and multi-turn requests with distinct session_id values to populate the in-memory sessions Map. New screenshot data points: - Uptime: 21h 56m - Requests: 24 / 0 active (up from 3 / 0) - Errors: 0 / 0 timeouts - Sessions: 8 active (was 0) - Plan Usage: 5h 20%, weekly 28% - byKey table: 11 keys with rich history, now including pi231-test + macbook-test rows from this round - Recent Requests: visible row count grown Multi-turn correctness was incidentally verified during seeding — a Pi231 request with session_id=pi-multi-01 turn 2 correctly recalled the number passed in turn 1 ("the number 7"), confirming session state persists across cli.js subprocess turns as designed. Capture method unchanged (Chrome headless, 1400x2400, --virtual-time-budget=14000). Doc-only change. server.mjs not touched. ALIGNMENT.md Rule 5 (cli.js citation) does not apply. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
047750e642 |
docs(images): replace dashboard.png with stats-populated version (#83)
#82 captured the dashboard mid-idle — server had been up 21h with zero traffic since restart, so the in-memory stats counters all showed 0 (Requests 0 / Errors 0 / Sessions 0). That made the top strip of the screenshot look like a dead service even though the historical byKey + Plan Usage data below were healthy. Re-captured after firing 3 small haiku requests through localhost to populate stats.totalRequests. The new screenshot now shows: - Status: ok / v3.13.0 - Uptime: 21h 39m (up from 21h 28m) - Requests: 3 / 0 active (was 0 / 0) - Plan Usage: 5h 17%, weekly 28% (was 13% / 27%) - Recent Requests: 2 visible rows showing model + latency + status (was empty) Same capture method as #82: Chrome headless --window-size=1400,2400 --virtual-time-budget=12000. The 3 priming requests were short haiku prompts ("reply with the single word OKn"), max_tokens=12 each. Plan-usage delta < 1%, byKey table already had 11 active keys with rich history so the priming did not distort the long-tail data. This addresses feedback that the previous screenshot's empty stats strip undermined the rest of the dashboard's data richness for first- time README readers. Doc-only change. server.mjs not touched. ALIGNMENT.md Rule 5 (cli.js citation) does not apply. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3bdeb50ed5 |
docs(images): refresh dashboard.png with current prod data (#82)
The previous dashboard.png was captured on day-1, before any real traffic — the screenshot was effectively empty (no Plan Usage bars, single API key, no Recent Requests). It made the Web Dashboard look like a placeholder rather than a working observability surface. Replaced with a fresh capture of the maintainer's Mac mini production OCP (running v3.13.0, multi auth) showing: - Status / uptime / active+queued counters - Plan Usage bars (5h: 13%, weekly: 27%) — real subscription draw - Usage by Key table — 11 active keys with request counts, success/error rates, average latency, last-seen timestamps - API Keys section — all 32 registered keys with truncated key prefixes + creation date + active/revoked status (truncation matches existing dashboard rendering, no full keys are exposed) - Recent Requests log — request stream with model, prompt size, latency Capture method: Chrome headless (no Playwright extension required) at 1400x2400, --virtual-time-budget=12000 to let dashboard JS finish fetching + rendering before the screenshot frame. PNG dimensions: 1400 x 2400 (was 1400 x 1739). The added height comes from the now-populated Usage by Key + API Keys + Recent Requests sections — not from layout changes. Doc-only change. server.mjs not touched. ALIGNMENT.md Rule 5 (cli.js citation) does not apply. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
fbbf3b6c7c |
docs(readme): add engineering-story backlink to 知乎 article + badge (#81)
Adds two pointers from README to a Chinese-language engineering blog post (https://zhuanlan.zhihu.com/p/2036388634207770402) that walks through OCP's cli.js-alignment philosophy, the 2026-04-11 drift incident (the 9-day hallucinated /api/oauth/usage endpoint), the three-tier guardrail design, and the recent fresh-state E2E pass that produced PRs #74-#78. Two minimal touches to README only: 1. Badges row: a "blog · engineering story" shield linking to the article. Slot fits between the existing Release badge and the Buy Me a Coffee badge so the row's visual rhythm is preserved. 2. New blockquote line at the end of §Why OCP? (between the single-maintainer / pre-1.0 disclosure and §Supported Tools). Brief, factual, no marketing voice. Why this PR The 知乎 piece is a self-contained engineering narrative about maintaining a single-maintainer LLM-assisted proxy without endpoint drift. README is the project's primary surface; pointing at the narrative lets readers who land on the repo decide if the project's discipline matters to them before they invest in install. Reverse direction: GitHub readers who follow the link bring some traffic back to the article, validating that engineering content has a home. Doc-only change. server.mjs untouched. ALIGNMENT.md Rule 5 (cli.js citation) does not apply. Same pattern as #68 / #69 / #71 / #78 / #79. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d760d7fcce |
docs(readme): add restrained star + issue CTA below tagline (#79)
* docs(install): remove false symlink claim, fix Anonymous mode startup command Three doc inaccuracies surfaced during fresh-state install testing on Pi231 + MacBook (Round 1+2): 1. README §Server Setup claimed setup.mjs "Symlink `ocp` to /usr/local/bin for CLI access" — false. setup.mjs writes start.sh + plist/systemd unit but creates no PATH symlink. Removed the line; added a short PATH tip showing the user's options (manual symlink to ~/.local/bin or /usr/local/bin, or shell alias) right after the install summary. 2. README §Anonymous Access told users to run `ocp start` to enable the feature — there is no `ocp start` subcommand. Available commands are restart / stop / status / logs / keys / usage / update / lan / health / clear / settings (verified via `~/ocp/ocp` enumeration). Replaced with the correct flow: export PROXY_ANONYMOUS_KEY, then `node setup.mjs --bind 0.0.0.0 --auth-mode multi`. 3. The same paragraph implied that exporting PROXY_ANONYMOUS_KEY in an interactive shell is enough to enable anonymous access — but the running proxy is auto-started by launchd/systemd from the service unit's own env, not from the user's shell. Spelled this out and noted that if OCP was installed before exporting the env var, the user must re-run setup.mjs (idempotent) so the service unit env is refreshed, then `ocp restart`. The PROXY_ANONYMOUS_KEY mechanism described becomes 100% accurate when PR B (`fix/setup-inject-service-env`, sibling PR) lands; current setup.mjs on main does not yet inject this env into the service unit. Doc-only — no `server.mjs` change, no version bump, no `cli.js` citation required. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(readme): add restrained star + issue CTA below tagline A single italic line under the existing personal-note italic, asking readers to ⭐ the repo if they get value, and to file issues — framed explicitly as "issues are even more useful than stars" so the CTA reads as feedback-seeking rather than vanity-metric chasing. Tone matches the rest of README: low-key, single-maintainer self-deprecating, no marketing voice. No "save money / free / $0" verbiage. Coexists with the existing buy-me-a-coffee personal-note line above it (different ask: funding vs. social-proof). This is doc-only. ALIGNMENT.md Rule 5 (cli.js citation) does not apply. Same pattern as #68 / #69 / #71 / #78. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
5e2effd05b |
fix(ocp-connect): write ~/.zshrc on macOS (default shell since Catalina) (#77)
macOS default shell has been zsh since Catalina (2019). The previous
rc-file selection logic treated macOS the same as Linux, so on a fresh
Mac where ~/.zshrc did not already exist AND the script was invoked via
`bash -s --` (e.g. `curl | bash`), the $SHELL guard was `/bin/bash`
and neither condition for zshrc was true — resulting in only ~/.bashrc
being written. Since ~/.bashrc is not sourced by interactive zsh
sessions, the OPENAI_BASE_URL / OPENAI_API_KEY exports were invisible
to interactive shells.
Fix:
- Add an explicit `elif $is_mac` branch that unconditionally includes
~/.zshrc: create the file (empty) if it does not yet exist, since
zsh tolerates an empty ~/.zshrc.
- On macOS, ~/.bashrc is only written if it already exists — consistent
with the task spec ("don't create ~/.bashrc if it didn't exist").
- Linux path is preserved unchanged.
- Fix the "Reload your shell" hint at script end: previously it printed
only the last loop variable `$rc_file` (stale reference outside the
loop). Now it iterates `${rc_files[@]}` so both files are shown on
macOS (reproducing the Round A bug: hint said only `source ~/.bashrc`
even when zshrc should also be reloaded).
Smoke-tested with HOME=/tmp/fakehome redirect for three scenarios:
1. Fresh MacBook (no .bashrc, no .zshrc) → only .zshrc created+written
2. macOS with existing .bashrc → both .bashrc and .zshrc written
3. Linux with bash, no .bashrc → .bashrc written (unchanged)
Identified during Round A testing on MacBook 2026-05-08.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
fb2d1d3feb |
fix(ocp-cli): replace eval-curl with bash array to preserve JSON body quoting (#74)
`eval curl "$_AUTH_HEADER" "$@"` re-tokenizes its argument list according
to bash word-splitting rules. When OCP_ADMIN_KEY is set, the JSON body
`'{"name": "laptop"}'` (which contains a space) gets split into
`'{name:'` and `'laptop}'` — two separate args — so curl receives a
malformed body and the server rejects the request.
Fix: replace the `_AUTH_HEADER` string + `eval` pattern with a bash array
`_AUTH_ARGS`. Array expansion via `"${_AUTH_ARGS[@]}"` preserves word
boundaries across substitution with no eval required. Both code paths
(OCP_ADMIN_KEY env var and ~/.ocp/admin-key file fallback) and the empty
case (no admin key) are preserved unchanged.
Verified via `bash -x` trace:
Before: `curl … -d '{name:' 'laptop}'` (body split, malformed)
After: `curl … -d '{"name": "testkey-pr-c"}'` (body intact, single arg)
Identified during fresh-state Round 2 testing on MacBook.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
12b09c236e |
fix(server): resolve claude binary from nvm/fnm/asdf and PATH fallback (#75)
Real-world macOS dev machines using nvm-managed Node hit a startup FATAL because the hardcoded candidate list in resolveClaude() only covered homebrew, /usr/local, /usr/bin, and ~/.local/bin. With Claude CLI installed at $HOME/.nvm/versions/node/<v>/bin/claude, the launchd job failed without manual CLAUDE_BIN injection. Fix: extend the candidate list with user-local Node version manager paths — nvm (with default-alias), fnm, asdf, and npm-prefix-relocated $HOME/.npm-global/bin. The existing CLAUDE_BIN env override and `which` fallback are preserved; resolution order is now explicit CLAUDE_BIN > hardcoded list > nvm/fnm/asdf > which > FATAL (with the message upgraded to mention CLAUDE_BIN as a hint). This is OCP-internal binary discovery — there is no `cli.js` operation to cite. ALIGNMENT.md Rule 2 (the rule that limits OCP to operations that exist in `cli.js`) does not constrain runtime path discovery for the OCP server itself. Smoke tests: - default (no CLAUDE_BIN): picks /opt/homebrew/bin/claude (unchanged) - CLAUDE_BIN=/nonexistent/claude: fail-fast preserved - HOME=/tmp/fakenvm with synthetic .nvm tree: candidate list contains the fake nvm path; alias-default unshift logic verified - npm test: 43/43 unit tests pass - node --check server.mjs: OK - alignment.yml blacklist grep: no hits Identified during fresh-state Round 2 testing on MacBook. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c0f2d3ab20 |
docs(install): remove false symlink claim, fix Anonymous mode startup command (#78)
Three doc inaccuracies surfaced during fresh-state install testing on Pi231 + MacBook (Round 1+2): 1. README §Server Setup claimed setup.mjs "Symlink `ocp` to /usr/local/bin for CLI access" — false. setup.mjs writes start.sh + plist/systemd unit but creates no PATH symlink. Removed the line; added a short PATH tip showing the user's options (manual symlink to ~/.local/bin or /usr/local/bin, or shell alias) right after the install summary. 2. README §Anonymous Access told users to run `ocp start` to enable the feature — there is no `ocp start` subcommand. Available commands are restart / stop / status / logs / keys / usage / update / lan / health / clear / settings (verified via `~/ocp/ocp` enumeration). Replaced with the correct flow: export PROXY_ANONYMOUS_KEY, then `node setup.mjs --bind 0.0.0.0 --auth-mode multi`. 3. The same paragraph implied that exporting PROXY_ANONYMOUS_KEY in an interactive shell is enough to enable anonymous access — but the running proxy is auto-started by launchd/systemd from the service unit's own env, not from the user's shell. Spelled this out and noted that if OCP was installed before exporting the env var, the user must re-run setup.mjs (idempotent) so the service unit env is refreshed, then `ocp restart`. The PROXY_ANONYMOUS_KEY mechanism described becomes 100% accurate when PR B (`fix/setup-inject-service-env`, sibling PR) lands; current setup.mjs on main does not yet inject this env into the service unit. Doc-only — no `server.mjs` change, no version bump, no `cli.js` citation required. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0d61da5153 |
fix(setup): inject CLAUDE_BIN, OCP_ADMIN_KEY, PROXY_ANONYMOUS_KEY into service env (#76)
Gap #1 partial (CLAUDE_BIN): setup.mjs now detects `which claude` at install time (or reads $CLAUDE_BIN) and writes CLAUDE_BIN into the service unit EnvironmentVariables dict. Works for nvm/homebrew paths not in server.mjs's hardcoded list, and for older server.mjs deployments. Gap #2 (OCP_ADMIN_KEY): reads $OCP_ADMIN_KEY from the user's shell env and conditionally injects it into the plist/systemd unit. Empty/unset → key is omitted entirely (server.mjs treats empty string as "no admin"). Key value is never logged; only its length is reported. Gap #6 partial (PROXY_ANONYMOUS_KEY): reads $PROXY_ANONYMOUS_KEY and conditionally injects it. Unset → key is omitted (anonymous access disabled). All three keys are read via process.env at install time; no new CLI flags. Injection status is logged before the !DRY_RUN guard so dry-run shows what would be written. Identified during fresh-state Round 2 testing. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
49baffe2da |
fix(setup): decouple OpenClaw config patch from being mandatory (#73)
OCP markets itself as a standalone OpenAI-compatible proxy with six
supported IDE clients (Cline / Cursor / Continue.dev / OpenCode / Aider /
OpenClaw). The README §Server Setup says "node setup.mjs" is the
installer entrypoint. But on a truly fresh box without OpenClaw
installed, setup.mjs hard-fails at line 111:
if (!existsSync(CONFIG_PATH)) fail(`OpenClaw config not found...`);
This contradicts the standalone-proxy stance and was caught during
PR #71 dogfood testing on Pi231.
Root cause: the OpenClaw config patch (lines 110-148) was baked in as
required because OCP started life as an OpenClaw-internal helper. The
project has since rebranded to standalone OCP without decoupling the
installer.
Fix: gate the OpenClaw config patch (Step 2) and auth-profiles patch
(Step 3) on existsSync(CONFIG_PATH). When OpenClaw is present, behavior
is byte-for-byte identical to current main (verified via dry-run hash
diff against ~/.openclaw/openclaw.json — UNCHANGED). When OpenClaw is
absent, the installer logs a graceful warning, skips both patch
sections, and continues to start.sh / launchd-plist / systemd-unit
creation as before. The summary banner is also conditional: when
OpenClaw is absent, it points the user at README § "Client Setup"
instead of giving openclaw.json edit instructions.
Also moves `import { readdirSync }` from mid-file (line 178, post-use)
to the top-level imports block; this was a latent ESM-hoisting quirk
that worked but is now syntactically required at the top because the
readdirSync call moved inside an `if` block.
Out of scope (Iron Rule 11, single-layer PR): server.mjs, models.json,
scripts/sync-openclaw.mjs, README.md, ALIGNMENT.md, package.json
version, the duplicate-spawn / health-verify logic at line 268+ (that's
a separate fix/setup-spawn-conflict PR).
Mac mini production unaffected: ~/.openclaw/openclaw.json already
exists there, so the OpenClaw-present path is preserved. `ocp update`
doesn't invoke setup.mjs, so running services are not touched.
Smoke-tested locally:
- Path 1 (OpenClaw present): dry-run output functionally identical to
current main; config sha256 unchanged after run.
- Path 2 (OpenClaw absent, OPENCLAW_STATE_DIR=/tmp/no-such-dir-*):
graceful warn, both patch sections skipped, banner shows
standalone-mode message, dry-run completes successfully.
- npm test: 43/43 pass.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
4b01d4e768 |
fix(setup): remove duplicate server spawn; verify health post-install (#72)
## Dogfood evidence (Pi231, 2026-05-08)
A user ran `node setup.mjs --bind 0.0.0.0 --auth-mode multi` and got:
- setup.mjs exit 0
- /health responded with authMode:"none" and server bound to 127.0.0.1 only
- ps showed two server.mjs processes: one orphan from setup (wrong config),
one systemd child restart-looping on EADDRINUSE
## Root cause (two-step conflict)
Step 6 (the deleted block) called `execSync('bash "${startPath}"')` which ran
start.sh's `nohup node server.mjs &` — spawning the server WITHOUT exporting
CLAUDE_BIND or CLAUDE_AUTH_MODE. That server ran with default bind=127.0.0.1
and authMode=none, ignoring the user's CLI flags.
Step 7 then wrote the systemd unit/launchd plist WITH the correct env vars and
bootstrapped the service — but port 3456 was already taken by Step 6's spawn,
causing EADDRINUSE and a silent restart loop. setup.mjs exited 0 because Step 6
had "succeeded" (a server was running, just the wrong one).
## What changed
1. Deleted Step 6 entirely (the `execSync('bash "${startPath}"')` block).
The systemd/launchd service installed in Step 7 is now the sole authoritative
start path. start.sh is unchanged and still available for manual non-systemd use.
2. Added Step 8 inside the `if (!DRY_RUN)` block: after Step 7 bootstrap,
waits 3 s, then GETs http://127.0.0.1:${PORT}/health with a 5 s timeout.
- On 200 OK: logs version, authMode, and bind socket (best-effort).
- On failure: prints clear error pointing to service logs, exits 1.
- Skipped when --no-start is set (existing flag).
Identified during PR #71 dogfood testing on Pi231 (RPi4 / Debian Bookworm).
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
||
|
|
36fa81d1e6 |
docs(install): add §Quick install with AI assistance + plug five new-user pitfalls (#71)
* docs(install): add §Quick install with AI assistance + plug five new-user pitfalls
Context: a returning user observed that letting an AI assistant follow the
README to install OCP would mostly work but stumble on a handful of small
gaps — missing OS qualifier, no admin-key generation hint, server IP
discovery buried, and a few common setup errors not in Troubleshooting.
This patch closes those gaps and adds a copy-paste prompt section for
new users who'd rather have an AI walk them through the install.
Five additive changes (README only, server.mjs untouched):
1. **§Server Setup prerequisites** — add the macOS/Linux qualifier
("Windows is not supported — setup.mjs installs launchd / systemd")
and `git`, both of which were implicit before.
2. **OCP_ADMIN_KEY generation hint** — replace the placeholder
`your-secret-admin-key` with a one-line `openssl rand -base64 32`
example, plus a reminder to add the export to ~/.zshrc / ~/.bashrc
so it survives shells.
3. **§Client Setup** — add an inline note pointing readers to run
`ocp lan` on the server to discover the server's LAN IP. Previously
`ocp lan` was mentioned only in §Server Setup, leaving client-side
readers to guess.
4. **§Troubleshooting** — three new entries for setup-time errors
(claude not found / EADDRINUSE 3456 / node version), with the
specific recovery commands. Existing entries left unchanged.
5. **§Installation → new ###Quick install with AI assistance subsection**
— three copy-paste prompts (single-machine, LAN server, client) that
pin the AI to the right README path, name the verification step, and
forbid silent retries. Includes a pointer to the manual handbook
sections for readers who prefer that path.
Doc-only change. server.mjs not modified. ALIGNMENT.md Rule 5 (cli.js
citation requirement) does not apply. Same pattern as #68, #69, #70.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(install): add Claude CLI install command + ####Headless install notes
Live Pi231 (RPi4 / Debian Bookworm) test of PR #71's "Quick install with AI
assistance → LAN server" prompt surfaced two more new-user pitfalls in the
README's Prerequisites + Server Setup flow:
1. **Claude CLI install command was missing.** Previous text said "Claude CLI
installed and authenticated" with a docs link — but the actual install
command (`npm install -g @anthropic-ai/claude-code`) appeared nowhere.
An AI assistant following the prompt has to fetch external docs to
guess at the install path. Now inlined.
2. **Headless servers (Pi / NAS / VPS) had no auth guidance.** OCP's main
deployment targets are always-on headless devices. `claude auth login`
actually works headless (prints URL + code, OAuth completes on any
browser-capable device), and `claude setup-token` provides a long-lived
token — but neither was documented. New ####Headless install notes
subsection explains both paths.
Test trail (Pi231):
- ✅ Linux (aarch64 Debian Bookworm), Node v22.22.2, git 2.39.5 — prereqs
satisfied except Claude CLI.
- ❌ `claude` not on PATH (expected — fresh Pi). README didn't tell the
user how to install it. → fix #1 above.
- ⚠️ Even after AI fetches the install command, headless OAuth was a
documentation gap. → fix #2 above.
Doc-only change. server.mjs not modified. ALIGNMENT.md Rule 5 does not
apply. Extends PR #71 (same install-UX layer per Iron Rule 11 IDR).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
cce0110253 |
docs(why-ocp): reorder bullets + soften alignment framing + visceral hooks (#70)
The "Why OCP?" section previously led with technical/governance bullets
(SSE heartbeat, alignment, models.json) and buried the most relatable
selling points (LAN multi-user keys, ocp-connect IDE auto-config, cache).
First-time readers stopped reading before reaching the bullets that would
have sold them on the project.
Changes (README.md only, line 19-26):
1. Reorder: human-relatable benefits first, governance discipline last.
New order: LAN multi-user → ocp-connect → cache → quota → SSE heartbeat
→ alignment → models.json. Quota is split out from the old combined
"quota + cache" bullet so each gets its own one-liner.
2. Soften the alignment bullet's framing. The previous prose flagged
"Other Claude proxies have shipped exactly that" and was deleted —
no need to call out competitors. Replaced with a measured "LLM-assisted
code drifts easily — it's tempting to invent plausible-looking endpoints
that cli.js doesn't actually use" plus a deliberately understated
payoff: "your setup keeps working when cli.js ships its next minor."
3. Add visceral hooks where bullets benefit from them:
- SSE heartbeat: "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" (frames the problem in user-felt terms before
describing the fix).
- Per-key quota: concrete example "set a kid's iPad to 20/day, a
partner's laptop to 100/week" (replaces abstract "limits per key").
4. Cache bullet now explicitly states the per-key isolation guarantee:
"cross-user pollution is impossible by hash construction, not by
application logic" — this addresses the most common pre-adoption
concern (and reflects the v3.13.0 D1 design).
5. Total length compressed ~20% despite added context — old bullets had
redundant "what" descriptions; new bullets lead with the "what for".
Doc-only change. server.mjs not touched. ALIGNMENT.md Rule 5 (cli.js
citation requirement) does not apply. Same pattern as #68, #69.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
40391791a1 |
docs(funding): raise sponsorship visibility — top badges + intro CTA + expanded support section (#69)
Context: Buy Me a Coffee + Stripe onboarding now fully live (verified buymeacoffee.com/dtzp555 returns og:type=profile with Support CTA, default $3 price tier, membership tier active). Previous §Support OCP at line 709 was effectively buried — last section before License, missed by most readers. Changes (README.md only, server.mjs untouched): 1. Top of file — three shields.io badges (License MIT, latest release, Buy Me a Coffee) under H1, before tagline. Standard OSS pattern (Vue / Vite / Tailwind), high visibility, doesn't disrupt prose. 2. Just under tagline — one-line italic personal note pointing to §Support OCP, with inline ☕ link as fallback for users who don't scroll. Quotes the spirit of the longer section without duplicating it. 3. §Support OCP expanded — adds the "open source from day one" framing (not freemium, not commercial-turned-open), the "my family uses it daily" angle, and an explicit feedback / issues invitation. The debugging-history paragraph is preserved verbatim. Doc-only change. ALIGNMENT.md Rule 5 (cli.js citation) does not apply — server.mjs is not modified. Same pattern as #68. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
342a0a44f5 |
docs(funding): add Buy Me a Coffee link + GitHub FUNDING.yml (#68)
- .github/FUNDING.yml — enables GitHub's native "Sponsor" button on the repo page, pointing to buymeacoffee.com/dtzp555. Other platforms (GitHub Sponsors, Ko-fi) are commented out and can be enabled later by uncommenting + filling in handles. - README.md § Support OCP — new section just before License. States the free-and-open-source commitment, lists the kinds of work that don't show up in commits (multi-machine debugging, IDE validation, drift incidents, concurrency leaks), and offers a single ☕ link for users who want to support continued maintenance. Explicitly disclaims paid tiers / premium features so the open-source posture stays unambiguous. server.mjs is not modified; this commit is doc-only and therefore exempt from the cli.js citation requirement (ALIGNMENT.md Rule 5 applies only to commits that touch server.mjs). Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9494fd6c69 |
chore(release): v3.13.0 — cache layer hardening (per-key isolation + bypass + chunked replay + singleflight) (#67)
Per release_kit overlay (CLAUDE.md § Iron Rule 5.5): - package.json bumped 3.12.0 → 3.13.0 - CHANGELOG.md updated with v3.13.0 entry - README.md § Response Cache updated to document the four hardening features This is a release preparation commit. Tag push to v3.13.0 will trigger .github/workflows/release.yml which auto-creates the GitHub Release. cli.js does not perform proxy-layer response caching, stampede protection, or replay; this release only ships internal cache-layer correctness and concurrency improvements that do not change the OpenAI-compatible wire surface visible to clients. No client-observable wire shape change. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>v3.13.0 |
||
|
|
5ff30ac9b6 |
feat(cache): singleflight stampede protection on non-streaming path (#66)
cli.js does not perform proxy-layer stampede protection. The singleflight layer is a value-add proxy operation that exists only inside OCP, between concurrent client requests and the single upstream cli.js spawn. It does not introduce, alter, or remove any endpoint, header, request field, or response field that cli.js emits or expects — no client-observable wire shape change. Justification under ALIGNMENT.md Rule 2: the singleflight Map deduplicates concurrent identical non-streaming cache-miss requests so only one cli.js spawn runs per unique hash window. All followers receive the same resolved (or rejected) content. This is a proxy-internal concurrency optimization, not a wire-level protocol change. Spec: docs/superpowers/specs/2026-05-07-cache-upgrade-design.md (D4) Changes: - keys.mjs: add singleflight(hash, fn) + getInflightStats() exports; in-memory Map cleared via Promise.finally() on each settlement - server.mjs: import singleflight + getInflightStats; wrap non-streaming cache-enabled path through singleflight with inner recheck; add TODO comment in callClaudeStreaming (streaming-path dedup is explicitly out of scope for v3.13.0, see spec D4 streaming caveat); extend /cache/stats to return inflight + requesters fields (additive, no removed fields) - test-features.mjs: 7 new PR-B singleflight tests covering basic dedup, failure fan-out, map cleanup (success + failure), different-hash independence, getInflightStats shape, and sequential-call non-sharing; all 31 tests pass (24 existing + 7 new) Streaming-path singleflight is explicitly out of scope; TODO left in callClaudeStreaming for a future follow-up ticket. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
16eeb66557 |
feat(cache): per-key isolation, cache_control bypass, chunked stream replay (#65)
* docs(governance): ADR 0005 (no multi-provider) + cache upgrade spec Governance prelude for the cache upgrade work: - docs/adr/0005-no-multi-provider.md — locks in the decision that OCP stays single-provider (Anthropic via cli.js spawn). Cache improvements are explicitly in scope (decision §3); multi-provider refactor is explicitly out of scope, with three documented trigger conditions for revisiting. - docs/adr/README.md — index updated to reference 0005. - docs/superpowers/specs/2026-05-07-cache-upgrade-design.md — design for the cache upgrade work split into PR-A (per-key isolation, cache_control bypass, chunked stream replay) and PR-B (singleflight stampede protection). Each design decision has a written rationale. server.mjs is not modified; this commit is doc-only and therefore exempt from the cli.js citation requirement (ALIGNMENT.md Rule 5 applies only to commits that touch server.mjs). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(cache): per-key isolation, cache_control bypass, chunked stream replay cli.js does not perform response caching at the proxy layer. OCP's response cache is a value-add operation internal to OCP, between the wire and the cli.js spawn. It does not introduce, rename, or alter any endpoint, header, request field, or response field that cli.js emits or expects — this change qualifies under ALIGNMENT.md Rule 2's value-add carve-out for non-wire- affecting proxy operations. no client-observable wire shape change. Spec: docs/superpowers/specs/2026-05-07-cache-upgrade-design.md D1 — Per-key cache isolation (cacheHash v2 format) keys.mjs: prepend `v2|k:<keyId or "anon">|` before existing hash fields. Backward-compatible: absent/null/empty keyId folds to "anon". v1-format rows in response_cache are abandoned naturally; TTL cleanup at server.mjs:185 reaps them within one window. No migration needed. server.mjs: pass keyId: req._authKeyId at the single cacheHash call site (line ~1221). D2 — cache_control bypass keys.mjs: export hasCacheControl(messages) — walks messages and nested content arrays for presence of cache_control field. server.mjs: if hasCacheControl(messages) is true, set req._cacheHash = null and log cache_skipped{reason: cache_control_present}; existing `if (CACHE_TTL > 0 && req._cacheHash)` guards on write-back handle the skip. D3 — Chunked stream replay (80 codepoints/chunk, no artificial delay) server.mjs: replace single-chunk cached.response emission with an Array.from(cached.response) loop in steps of CACHE_REPLAY_CHUNK_SIZE=80. Array.from ensures multibyte UTF-8 codepoints (e.g. CJK) are never split. Tests: 12 new cases in test-features.mjs (36 total, 0 failed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
c998d21a4f |
chore(ci): add gitleaks workflow to scan PRs and pushes to main (#64)
The repo has shipped `.gitleaks.toml` (with project-specific allowlist entries — public OAuth client ID, README placeholders, an old plan doc) since the privacy remediation work, but no GitHub Action invoked it. The config was orphan: real protection only when someone ran gitleaks locally, never gating merges. This workflow wires `.gitleaks.toml` into CI: - Triggers on every `pull_request` (any branch) and `push` to `main`. - Uses `gitleaks/gitleaks-action@v2`, which auto-detects the repo-root `.gitleaks.toml` and applies its allowlist. - Hard-fails on any leak. No `continue-on-error`. Public repo policy. - `permissions: contents: read` — minimum required scope. - `fetch-depth: 0` so the action can scan full history (the action's default behavior; explicit here for clarity). Verification path: - The workflow runs on this PR itself; if any secret were ever committed to the repo, the scan fails here. Prior audit confirmed the tracked tree is clean of real secrets, so this PR's own scan should pass. Refs: audit side-finding 4 of 4. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
5be369ed68 |
docs(changelog): normalize version heading format to ## vX.Y.Z — YYYY-MM-DD (#63)
Audit side-finding: heading style drift in CHANGELOG.md. Before: ## v3.12.0 (2026-04-25) ← parens style (1 entry) ## v3.11.1 — 2026-04-21 ← em-dash style (canonical, majority) ## v3.11.0 — 2026-04-20 ← em-dash style (canonical, majority) After: all 3 entries use the em-dash form (2 of 3 entries already used it, so it's the canonical pattern by majority). Only the v3.12.0 heading line changed. The body content under each heading is untouched — only the date-format separator changes from parens to em-dash. Verification: - `grep "^## " CHANGELOG.md` after the edit → all entries match `## vX.Y.Z — YYYY-MM-DD`. - `git diff CHANGELOG.md` shows exactly one line changed. Refs: audit side-finding 3 of 4. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
3d52ffc152 |
chore(deps): bump engines to >=22.5 to match node:sqlite usage in keys.mjs (#62)
OCP uses Node.js built-in SQLite (`node:sqlite`) in keys.mjs:3 for the
LAN-mode key store. The `node:sqlite` module is only available in:
- Node 22.5.0+ behind --experimental-sqlite flag
- Node 23.0.0+ without any flag (fully stable)
The previous `engines: ">=18"` was inaccurate and would have caused
opaque "Cannot find module 'node:sqlite'" failures for users on
Node 18-22.4 the moment LAN mode (multi-key) was enabled.
Changes:
- package.json: engines.node ">=18" → ">=22.5"
- README.md: prerequisite "Node.js 18+" → "Node.js 22.5+ (Node 23+ recommended …)"
with a one-line note explaining the flag distinction so users on 22.x know
they may need --experimental-sqlite.
Verification:
- Source usage of node:sqlite confirmed via grep: keys.mjs:3
(`import { DatabaseSync } from "node:sqlite";`).
- Local node --version = v25.8.0 (well above 22.5); `npm install` exit 0
with no engine-mismatch warning.
- No other source file imports node:sqlite (single point of usage).
Refs: audit side-finding 2 of 4.
Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
68b0838074 |
chore(deps): delete stale package-lock.json (was v3.4.0 vs package.json v3.12.0) (#61)
The repo-tracked package-lock.json declared "version": "3.4.0" while package.json is at v3.12.0 — 8 minor versions of drift. Since package.json has zero dependencies (only built-in node:sqlite, node:http, node:https), the lockfile carried no useful information. It was pure noise that would mislead anyone running `npm ci` into thinking they were installing v3.4.0. Verification: - package.json has no `dependencies` or `devDependencies` field (grep -A2 '"dependencies"' package.json → no match). - Fresh clone + checkout + `npm install` → exit 0, "audited 1 package in 93ms, found 0 vulnerabilities". - node_modules is correctly empty after install (no bin shims to relink). Refs: audit side-finding 1 of 4. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
313cb13a78 |
docs: align README + governance docs with current state (uninstall, files, ADR index, ship-archive) (#59)
Multiple documentation polish items rolled into one PR (one layer: "docs alignment with current state"). ### README.md - **Uninstall section** added between Server Setup and Client Setup (was missing — `node uninstall.mjs` exists but went undocumented). - **OpenClaw definition** added as a footnote on first README mention (the architecture diagram and Supported Tools table both reference OpenClaw without ever defining it). - **Repository Layout section** added before Security — table of top-level files (server.mjs, setup.mjs, uninstall.mjs, keys.mjs, models.json, ocp/ocp-connect, dashboard.html, scripts/, .claude/skills/, ocp-plugin/, docs/adr/, ALIGNMENT.md, AGENTS.md, CLAUDE.md) so a new contributor knows what each file does. - **LICENSE link** added to the License section footer. ### docs/adr/README.md (new) - Index of the three published ADRs (0002, 0003, 0004) with a one-line description each. - Explains the `0001` placeholder (early internal proposal that was superseded; numbering deliberately starts at `0002`). - Guidance on when to write a new ADR vs. when a commit message suffices. ### Spec/plan housekeeping - `specs/.gitkeep` removed (the empty `specs/` placeholder confused the picture; canonical paths are `docs/superpowers/plans/` for active plans and `docs/superpowers/specs/` for long-lived design docs that other code references). - Shipped plans moved to `docs/superpowers/plans/shipped/`: - `2026-04-10-lan-mode.md` (shipped: README LAN mode section) - `2026-04-25-47-sse-heartbeat-plan.md` (shipped: v3.12.0 per CHANGELOG) - `2026-04-25-47-sse-heartbeat-design.md` left in `docs/superpowers/specs/` unchanged because both `server.mjs:565` and `CHANGELOG.md:7` link to that exact path; moving it would require a `server.mjs` edit, which needs `cli.js` citation per ALIGNMENT.md Rule 1. ### AGENTS.md - Updated "Key files to know" to add `docs/adr/README.md`, `docs/superpowers/plans/`, and `memory/constitution.md`. - Note explaining `memory/constitution.md` is spec-kit's standard location, distinct from `~/.cc-rules/memory/` and `ALIGNMENT.md`. - Updated "Handoff expectations" item 5 from `docs/superpowers/specs/*/tasks.md` (which never matched anything — there were no `tasks.md` files there) to `docs/superpowers/plans/` (excluding `shipped/`). ### Coordination with PR #53 PR #53 is open and adds "Why OCP?", "Comparison", and "Governance" sections to README. This PR deliberately avoids those areas — only edits the Supported Tools table footnote, inserts Uninstall before Client Setup, inserts Repository Layout before Security, and updates the License footer. No expected merge conflict. Refs: audit findings M6, M8, M9, M11, L2, L3, L6. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
e4b010af5e |
docs(readme): add Why OCP, comparison table, governance section (#53)
Adds three positioning sections to make the README convert clones-to-stars better: 1. "Why OCP?" near the top — 6 differentiator bullets with evidence links (SSE heartbeat / ALIGNMENT.md / models.json SPOT / multi-key / per-key quota / ocp-connect). 2. "Comparison" subsection — honest table vs claude-code-router and anthropic-proxy. Acknowledges CCR's larger ecosystem; positions OCP as cli.js-aligned + subscription-multiplexing focused. 3. "Governance" section near the bottom — links to ALIGNMENT.md, AGENTS.md, ADRs, alignment.yml. Consolidates the governance-link surface in one place rather than scattered. Net diff +43 -0. No content removed. No anchors broken. No code changes. Co-authored-by: dtzp555 <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0752f666fb |
test: wire test-features.mjs to npm test + add minimal CI smoke workflow (#60)
`test-features.mjs` shipped at v3.8.0 (per CHANGELOG of the keys.mjs quota+cache work) and is referenced from AGENTS.md as the project's only test artifact, but until now nothing actually ran it — no `npm test` script, no CI step. Wiring it up so it runs on every push and PR. ### Changes - `package.json`: add `"test": "node test-features.mjs"` to scripts. - `.github/workflows/test.yml` (new): single-job workflow that runs `npm test` on push to main and on every PR. Uses Node 24 because `keys.mjs` imports `node:sqlite`, which is stable in Node 23+ (Node 24 is the current LTS; Node 22 would need `--experimental-sqlite`). No `npm install` step — OCP has zero external runtime dependencies per `package-lock.json`. - `AGENTS.md`: note that `test-features.mjs` runs via `npm test` and is enforced by `.github/workflows/test.yml`. ### Why this is a hard check, not a soft check `test-features.mjs` is self-contained — it imports `keys.mjs` and exercises the SQLite-backed key/quota/cache code paths against a throwaway test DB at `~/.ocp/ocp-test.db`. It does NOT require: - a live claude CLI binary - a running OCP server - any network access So CI can run it as a real check; no `continue-on-error` needed. ### Local verification ``` $ npm test [...] === Results: 24 passed, 0 failed === ``` 24 assertions cover createKey / listKeys / quota math / cache hash determinism / cache TTL / clearCache. Exit code is 1 on any failure (`process.exit(failed > 0 ? 1 : 0)` at the bottom of test-features.mjs). ### Future expansion If the suite later grows to include tests that DO require a live claude CLI or a running OCP, mark those steps `continue-on-error: true` (or split them into a separate job). The comment in `test.yml` documents this contract. Refs: audit (test-features.mjs orphan / unrunnable in CI). Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
ae4a829904 |
chore(naming): rename package + code refs from openclaw-claude-proxy to OCP (#57)
The npm name `ocp` is squatted (deprecated `node-ocp` v0.0.1), so the
package name is renamed to `open-claude-proxy` (verified via
`npm view open-claude-proxy` → 404, confirming availability).
### Changes
- `package.json`:
- `"name"`: `openclaw-claude-proxy` → `open-claude-proxy`
- `"bin"` map UNCHANGED: both `openclaw-claude-proxy` and `ocp`
binaries still resolve, preserving back-compat for existing installs
that reference `openclaw-claude-proxy` as a CLI command.
- `setup.mjs`: header JSDoc + start.sh banner string
- `uninstall.mjs`: header JSDoc
### Intentionally NOT changed
- `server.mjs` startup banner (line 1628):
`console.log('openclaw-claude-proxy v...')`. Editing `server.mjs`
requires `cli.js:NNNN` citation per ALIGNMENT.md Rule 1, and a
cosmetic log-string rename has no `cli.js` correspondence. Deferred
— would need an ALIGNMENT.md scope justification PR if pursued.
- `bin/openclaw-claude-proxy` symlink/path: existing installs depend on
this name; kept as a back-compat alias.
### Evidence
```
$ npm view ocp
ocp@0.0.1 | DEPRECATED — squatted by node-ocp
$ npm view open-claude-proxy
404 — available
```
Refs: audit (naming consistency).
Co-authored-by: dtzp555 <dtzp555@gmail.com>
|
||
|
|
51e908e145 |
chore(repo): remove broken/undocumented Docker files (#58)
The Dockerfile, docker-compose.yml, and .dockerignore are removed because the Dockerfile is structurally broken AND Docker is not a documented runtime for OCP. ### Evidence — Dockerfile is broken The Dockerfile COPYs only 3 files: ``` COPY server.mjs ./ COPY setup.mjs ./ COPY package.json ./ ``` But `setup.mjs:43` reads `models.json` at runtime (`JSON.parse(readFileSync(join(__dirname, "models.json"), ...))`), and `server.mjs` requires `keys.mjs`, `dashboard.html`, etc. The container as built would crash on first run. ### Evidence — Docker is undocumented ``` $ grep -i docker README.md AGENTS.md CLAUDE.md # 0 hits ``` No README install path, no AGENTS.md runtime mention, no CLAUDE.md release-kit reference. These files were ghost infrastructure. ### Why delete vs. fix Fixing the Dockerfile to actually work would require: COPYing 6+ files, verifying the container can write to `~/.openclaw/`, deciding on auth mounting (claude CLI binary + auth state), and adding documentation across README/AGENTS/CLAUDE. None of that is justified by a request. Easier to remove the dead files now and reintroduce a working Dockerfile when there's an actual demand for containerised OCP, with proper documentation alongside. Refs: audit (dead infrastructure cleanup). Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
d99534dc35 |
chore(repo): add .gitignore for runtime artifacts; commit scripts/heartbeat-field-check.sh (#55)
Two related changes — both repo-hygiene, no behavior impact. ### `.gitignore` (new) Ignores runtime artifacts that should never be tracked: - `logs/` and `*.log` — proxy.log, last_send.log, heartbeat-field-check log - `node_modules/` — dependency cache - `.env`, `.env.*` — local secrets/config - `.DS_Store`, `*.swp`, `*~` — editor/OS scratch `logs/` was previously untracked-but-present in working trees; the new ignore makes that intent explicit and prevents accidental commits. ### `scripts/heartbeat-field-check.sh` (commit existing untracked file) This script was authored as a one-shot field-evidence gatherer for the v3.12.0 SSE heartbeat work (PR #49 / issue #47). It fired successfully on 2026-05-02 09:00 Australia/Brisbane via launchd (`~/Library/LaunchAgents/dev.ocp.heartbeat-check.plist`) and posted a summary comment to issue #47. Useful tooling pattern (one-shot field check + launchd schedule + dry-run flag), worth keeping under version control rather than letting it die in an untracked working tree. Verification: `git status` clean after both adds. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
39ca20536e |
docs(governance): correct CLAUDE.md + AGENTS.md to reflect actual alignment.yml blacklist (#56)
Both `CLAUDE.md` (line 25) and `AGENTS.md` (line 46) previously claimed the `alignment.yml` blacklist included two tokens — `api/oauth/usage` and `api/usage`. The actual workflow has a single token in `BLACKLIST`: `api.anthropic.com/api/oauth/usage`. Doc-vs-CI drift. Fix is doc-side only — `alignment.yml` itself is unchanged because adding a second blacklist token (`api/usage`) is a constitutional change that requires an `ALIGNMENT.md` amendment PR per ALIGNMENT.md Amendment Procedure. If `api/usage` should be added to the blacklist, that's a separate PR with ALIGNMENT.md amendment + reviewer sign-off. Refs: audit findings (governance doc accuracy). Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
8b3f50912e |
chore(repo): remove v2.x stale openclaw-claude-proxy/ folder + dead start.sh (#54)
Both removed: - `openclaw-claude-proxy/` — v2.4.0 stale duplicate of the proxy. Current proxy is `server.mjs` at repo root (v3.x); the nested folder was an early packaging artifact that never got deleted. Audit finding H1. - `start.sh` — hardcoded `/Users/taodeng/.openclaw/...` paths that only ever worked on the original maintainer's home directory. The real install is produced by `setup.mjs` (see setup.mjs:212-237 which writes the correct per-user start hook). Audit findings H5, H6 (path leak + dead script). Verification: `git grep "/Users/taodeng/"` returns 0 hits in tracked files. Refs: audit findings H1, H5, H6. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
780462c763 |
fix(cli): set DISABLE_AUTOUPDATER=1 in manual restart fallback (#52)
When ocp restart falls back to nohup (no launchctl/systemd service registered), prepend DISABLE_AUTOUPDATER=1 so the spawned server.mjs and any claude subprocess it spawns skip the in-binary auto-updater check. Why: claude-code's native binary contains an auto-updater that fires after each successful invocation. Partial install.cjs failures during update leave bin/claude.exe as an ASCII shim → spawnSync ENOEXEC → OCP slot lockup (symptoms in #37, #40 forensics). Setting this env at the launch site stops the trigger. Note: only covers the manual nohup path. Users with launchctl plist or systemd unit must add the env there too (plist EnvironmentVariables or systemd Environment=). Authoritative settings location is ~/.claude/settings.json env key — see learnings/claude_code_disable_autoupdate.md in cc-rules for the full 4-layer guidance. Verified: 24h+ stable on Mac mini after applying full multi-layer fix on 2026-04-30 04:20 (claude.exe mtime unchanged, OCP uptime 22h54m, 0 errors over 3 real calls). No server.mjs change — ALIGNMENT.md unaffected. Co-authored-by: dtzp555 <dtzp555@gmail.com> |
||
|
|
733a2ed4c2 |
chore(server): add session-create-vs-resume + lifetime instrumentation (refs #41 #42) (#51)
Adds structured logging for the two forensic candidates so the eventual fixes can be evidence-driven (per #41/#42 bodies: "Evidence needed before code change — do not patch speculatively"). NO bug fix in this PR — the stale-create-entry behavior (#41) and the lastUsed-resistant-expiry behavior (#42) are intentionally left unchanged so they can be observed in production /logs. Three changes (server.mjs): 1. Session-create branch records `firstSeen` alongside `lastUsed` (same timestamp), giving the sweep loop an absolute-age signal independent of the actively-bumped `lastUsed` field. Resume branch is untouched so `firstSeen` is preserved across resumes. 2. handleSessionFailure now emits a structured `session_failure` event with `mode: "resume"` (action: "deleted") OR `mode: "create"` (action: "kept"). The "kept" branch makes #41's "stale create entries never deleted" pattern visible in /logs without changing behavior. 3. TTL sweep emits `session_expired` (info, with idleMs + ageMs) on actual expiry, and `session_long_lived` (warn) when ageMs > 4×TTL but the entry is not being expired this tick. The latter is the #42 evidence trigger — a session whose lastUsed keeps getting bumped and so resists idle-based expiry. cli.js citation: N/A — logEvent payloads are OCP-internal observability, never sent on the wire to the Anthropic API. ALIGNMENT.md Rule 2 (no invention) is scoped to endpoints/headers/request/response fields, not to internal server logging. Independent reviewer: fresh-context sonnet APPROVE_WITH_MINOR. Two minor notes — repeated `session_long_lived` emissions per 60s sweep tick is expected (downstream analysis dedupes by conversationId). LOC: +19 / -3 = +16 net. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
ca2d23d230 |
chore(speckit): add compatibility/metadata frontmatter to SKILL.md files (refs #39) (#50)
Backfill the two YAML frontmatter blocks that upstream
`specify_cli/agents.py:build_skill_frontmatter()` emits:
compatibility: "Requires spec-kit project structure with .specify/ directory"
metadata:
author: github-spec-kit
source: claude:templates/commands/<cmd>.md
Applied uniformly across all 9 speckit-* SKILL.md files. Each file gets the
two blocks appended to its existing frontmatter (between the closing fields
and the closing `---`). No functional change — these fields are informational
only, per #39 body and PR #38 reviewer note.
Verified:
- All 9 files have `compatibility:` key (grep -lc, 9/9)
- All 9 files have `metadata:` block with `author: github-spec-kit` (9/9)
- All 9 files have `source: claude:templates/commands/<cmd>.md` matching the
command name (9/9)
- No existing fields modified or removed; pure additive +4 lines per file
Upstream reference (build_skill_frontmatter):
https://github.com/github/spec-kit/blob/main/src/specify_cli/agents.py
|
||
|
|
b871b72b6b |
feat(server): SSE heartbeat on streaming path (#47) (#49)
* docs(spec): design for #47 SSE heartbeat on streaming path Draft spec for an opt-in idle-watchdog SSE heartbeat covering both pre-first-byte and mid-stream silent windows. Default disabled, controlled by CLAUDE_HEARTBEAT_INTERVAL. Targets ~40 LOC. Decisions captured: D1 whole-stream reset-on-byte; D2 SSE comment frame; D3 default disabled; D4 relocate ensureHeaders() to post-spawn; D5 X-Accel-Buffering: no on both SSE header sites; D6 single log line per affected request. Scope-locked: does not touch CLAUDE_TIMEOUT semantics, the separate server.mjs:480-489 dangling-client bug, issues #41/#42, or the non-streaming path. Includes privacy preflight and cloud-testing plan per maintainer feedback. Refs: #47 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(plan): implementation plan for #47 SSE heartbeat 6 phases: pre-work (file 480-bug), implementation (5 tasks on feat/47-sse-heartbeat), opus fresh-context review, cloud verification on Mac rig, privacy preflight, PR+release. Each implementation task carries concrete code, syntax check, and a scoped commit message. LOC budget enforced in Task 1.6 gate (~45 server.mjs lines max). Reviewer checklist scopes scope-lock, ALIGNMENT, privacy, and heartbeat-cannot-abort discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): add startHeartbeat helper + HEARTBEAT_INTERVAL env var Per design doc (refs #47). Helper is a per-request idle watchdog that emits `: keepalive\n\n` SSE comment frames; returns a {reset, stop} handle. No wiring yet — helper is unused, safe to commit in isolation. cli.js citation: N/A — SSE response shaping is an OCP-owned translation layer, not a cli.js operation. See AGENTS.md and ALIGNMENT.md Rule 2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(server): eagerly send SSE headers post-spawn (D4, refs #47) Moves the ensureHeaders() call from "on first stdout byte" to "immediately after successful spawn." This is a prerequisite for the heartbeat covering the pre-first-byte silent window (the 'processing large contexts' failure mode in #47). Behavioral consequence: the narrow "spawn succeeded but subprocess died before any byte" branch at server.mjs:610-611 becomes effectively dead in the common case. The post-headers SSE-stop path (612-619) handles it instead. The branch remains defensively for the client-closed-before- ensureHeaders race. Isolated commit per design doc §D4 so reviewer can focus on this one behavior change. cli.js citation: N/A — SSE header emission is OCP response-shaping. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): wire heartbeat into streaming path (refs #47) - sendSSE() accepts optional hb handle and calls hb.reset() before write - callClaudeStreaming starts heartbeat after ensureHeaders() and passes hb to the three streaming sendSSE call sites - All three exit paths (proc close, proc error, res close) call hb.stop() to guarantee timer cleanup; no-op handle when disabled means zero runtime cost when CLAUDE_HEARTBEAT_INTERVAL=0 Heartbeat never aborts — only writes comment frames and re-arms. Aligns with v3.3 timeout discipline (single CLAUDE_TIMEOUT, no secondary client-killing timers). cli.js citation: N/A — SSE response shaping is OCP translation layer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(server): add X-Accel-Buffering: no to SSE response headers (refs #47) nginx (and many LBs / Cloudflare) default to proxy_buffering=on, which would buffer heartbeat comment frames indefinitely and defeat the feature silently. This header hints no-buffering; other stacks ignore it. Applied at both SSE header sites (real streaming + cache-hit). cli.js citation: N/A — response header shaping is OCP translation layer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(release): v3.12.0 — streaming heartbeat (refs #47) Bundles the release-kit companion files per Iron Rule 5.2 / 11 example: version bump across package.json + ocp-plugin + openclaw.plugin.json, CHANGELOG v3.12.0 section, README env var row + "Streaming heartbeat" explainer. Tag push to v3.12.0 triggers .github/workflows/release.yml to create the GitHub Release automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(server): ensureHeaders returns true when already sent (refs #47) Phase 3 smoke test revealed every content chunk was being dropped after the D4 eager ensureHeaders() call: the stdout.on('data') guard `if (!ensureHeaders()) return;` early-returned on every chunk because ensureHeaders returned false for the already-sent case (conflated with the dead-connection case). Split the two conditions explicitly: return false only when res is ended/destroyed; return true when headers are (already or now) sent. This also fixes a latent multi-chunk bug on main that was masked because claude CLI typically outputs in one stdout chunk. Verified: node -c server.mjs; subsequent re-run of Phase 3 smoke test now sees streaming content chunks + heartbeat frames. cli.js citation: N/A — SSE response shaping is OCP translation layer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>v3.12.0 |
||
|
|
cff06439fa |
chore(privacy): remediation follow-up from ocp#44 (#45)
Three-part follow-up from the 2026-04-22 privacy postmortem:
1. OAUTH_CLIENT_ID verified as public Claude Code constant (not a secret).
Added gitleaks allowlist entry. The value 9d1c250a-e61b-44d9-88ed-5944d1962f5e
is the public PKCE client ID used by the Claude Code CLI — public clients in
PKCE flows have no client secret, so the ID itself carries no secret value.
Introduced in commit
|
||
|
|
1c29f4867f |
chore(privacy): scrub personal identifiers from public repo (#43)
Removes all occurrences of maintainer's real name and handle from tracked files in this PUBLIC repository. Replaces with generic terms like "project maintainer" / "the maintainer". Files scrubbed: - CLAUDE.md (2 instances) - ALIGNMENT.md (Auditor field) - docs/adr/0002, 0003, 0004 (Authors/Deciders fields + body text) - memory/constitution.md (1 instance) - docs/superpowers/plans/2026-04-10-lan-mode.md (Users/taodeng paths → $HOME) Also: - Replaces specific machine hostnames (Taos-Mac-mini, Taos-MacBook-Pro) with role-based names (home-mac, travel-macbook) in ADR 0001 (if committed; currently untracked). - Replaces /Users/taodeng/ paths with $HOME/ in old plan doc. NOTE: git history still contains the personal info. See postmortem (issue TBD) for whether to rewrite history via git-filter-repo. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
cdd6b41261 |
fix(concurrency): release slot on subprocess SIGTERM failure (#40)
Per forensic analysis in #37, the timeout handler at server.mjs:471 called `proc.kill('SIGTERM')` without decrementing the concurrency counter (`stats.activeRequests`). If the subprocess was stuck in a syscall (e.g. MCP I/O) and ignored SIGTERM, its slot was not freed until — and only if — the `proc.on('close')` handler eventually fired after the 5s SIGKILL escalation successfully reaped the child. Real-world observation (#37) shows 3 stuck claude subprocesses running 20 minutes to 2h 45m, exhausting the 8-slot pool and returning `500 concurrency limit reached (8/8)` on every subsequent request. Fix: attach `cleanup` to `proc.once('exit', ...)`. The 'exit' event fires before 'close' and runs on every termination path — normal completion, error, SIGTERM, SIGKILL, crash — even if stdio pipes stay open. `cleanup()` is already idempotent (guarded by `cleaned` flag), so this listener is safe alongside the existing 'close' and 'error' paths that also call it. ALIGNMENT: cli.js has no equivalent subprocess-pool / concurrency- limit logic — cli.js is a single-user CLI, the process being pooled, not the pool itself. Per ALIGNMENT.md Rule 2, this fix is proxy- internal (subprocess lifecycle management) with no cli.js equivalent to cite; documented here instead. No wire-protocol, HTTP surface, or response shape change (Rule 3 preserved). Release kit: bumps version to 3.11.1 and adds CHANGELOG entry so the auto-release workflow tags v3.11.1 on merge. README version refs are feature-gate markers for v3.11.0 (models.json SPOT / auto-sync) and remain historically accurate — no README change needed. Fixes #37. Co-authored-by: dtzp555 <dtzp555@gmail.com>v3.11.1 |
||
|
|
9facd8307a |
feat(speckit): integrate github/spec-kit for spec-driven development (#38)
Installs spec-kit slash commands (/speckit-specify, /speckit-plan, /speckit-tasks, /speckit-implement, /speckit-analyze, /speckit-clarify, /speckit-checklist, /speckit-constitution, /speckit-taskstoissues) into Claude Code via .claude/skills/speckit-*/. Note: specify-cli v1.0.0 init fails on Windows because spec-kit v0.5+ releases no longer attach binary zip assets to GitHub releases (confirmed by inspecting all 30 releases via API; only v0.4.4 and earlier have assets). Templates were sourced directly from the github/spec-kit@v0.7.3 repo tree and SKILL.md files were built per the claude integration spec in src/specify_cli/integrations/claude/__init__.py (user-invocable:true, disable-model-invocation:false, argument-hint injection). Enables the "specs in git" workflow for cross-device work state: - New feature → /speckit-specify → specs/NNN/spec.md - Design → /speckit-plan → specs/NNN/plan.md - Work → /speckit-tasks → specs/NNN/tasks.md (handoff artifact) - Implement → /speckit-implement → code Preserves existing CLAUDE.md (@AGENTS.md bridges intact) and existing memory policies in AGENTS.md. memory/constitution.md is annotated at the top to reference AGENTS.md for project-specific constraints. No change to server.mjs, models.json, package.json, or any other code. Co-authored-by: Tao Deng <dtzp555@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
2ecc4945ad |
docs(governance): add AGENTS.md bridge + 3 historical ADRs (#36)
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> |