mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 20:14:34 +00:00
Merge pull request #1669 from kaitranntt/kai/docs/1666-pruning-freshness
docs: prune stale guides and enforce freshness
This commit is contained in:
25 files changed
+687
-2331
No files matched your search
@@ -1,6 +1,6 @@
|
||||
name: Documentation improvement
|
||||
description: Report missing, outdated, or confusing docs.
|
||||
title: "docs: "
|
||||
title: 'docs: '
|
||||
labels:
|
||||
- documentation
|
||||
body:
|
||||
@@ -24,7 +24,7 @@ body:
|
||||
attributes:
|
||||
label: File or page
|
||||
description: Path or URL if you know it.
|
||||
placeholder: README.md or docs/cursor-integration.md
|
||||
placeholder: README.md or https://docs.ccs.kaitran.ca/providers/oauth/cursor
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
|
||||
@@ -1,29 +1,44 @@
|
||||
name: Docs – Quickstart Snippet Parity
|
||||
name: Docs – Parity and Freshness
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "docs/quickstart-snippet.md"
|
||||
- "README.md"
|
||||
- "docker/README.md"
|
||||
- "tests/docs/quickstart-parity.sh"
|
||||
- ".github/workflows/docs-parity.yml"
|
||||
- 'docs/quickstart-snippet.md'
|
||||
- 'README.md'
|
||||
- 'docker/README.md'
|
||||
- 'macos-bar/README.md'
|
||||
- 'docs/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- 'src/**'
|
||||
- 'scripts/hardening-inventory.js'
|
||||
- 'scripts/maintainability-metrics.js'
|
||||
- 'scripts/runtime-source-classifier.js'
|
||||
- 'tests/docs/quickstart-parity.sh'
|
||||
- 'tests/docs/documentation-freshness.js'
|
||||
- '.github/workflows/docs-parity.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/quickstart-snippet.md"
|
||||
- "README.md"
|
||||
- "docker/README.md"
|
||||
- "tests/docs/quickstart-parity.sh"
|
||||
- ".github/workflows/docs-parity.yml"
|
||||
- 'docs/quickstart-snippet.md'
|
||||
- 'README.md'
|
||||
- 'docker/README.md'
|
||||
- 'macos-bar/README.md'
|
||||
- 'docs/**'
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- 'src/**'
|
||||
- 'scripts/hardening-inventory.js'
|
||||
- 'scripts/maintainability-metrics.js'
|
||||
- 'scripts/runtime-source-classifier.js'
|
||||
- 'tests/docs/quickstart-parity.sh'
|
||||
- 'tests/docs/documentation-freshness.js'
|
||||
- '.github/workflows/docs-parity.yml'
|
||||
|
||||
jobs:
|
||||
quickstart-parity:
|
||||
name: Assert quickstart snippet matches in README.md and docker/README.md
|
||||
docs-parity:
|
||||
name: Assert documentation parity and freshness
|
||||
if: >-
|
||||
contains(fromJSON('["COLLABORATOR","MEMBER","OWNER"]'), github.event.pull_request.author_association)
|
||||
|| github.event_name == 'push'
|
||||
runs-on: [self-hosted, linux, x64, cliproxy]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -32,3 +47,9 @@ jobs:
|
||||
|
||||
- name: Run quickstart parity check
|
||||
run: bash tests/docs/quickstart-parity.sh
|
||||
|
||||
- name: Validate documentation pointers and relative links
|
||||
run: node tests/docs/documentation-freshness.js
|
||||
|
||||
- name: Validate generated hardening inventory
|
||||
run: node scripts/hardening-inventory.js --check
|
||||
@@ -94,7 +94,8 @@ The proxy also supports request-time `profile:model` selectors, scenario-based
|
||||
model routing through `proxy.routing`, and explicit activation helpers such as
|
||||
`ccs proxy activate --fish`.
|
||||
|
||||
Guide: [OpenAI-Compatible Provider Routing](./docs/openai-compatible-providers.md)
|
||||
Guide:
|
||||
[OpenAI-Compatible Provider Routing](https://docs.ccs.kaitran.ca/features/proxy/openai-compatible-providers).
|
||||
|
||||
### Related Project: claude-code-router
|
||||
|
||||
@@ -166,7 +167,7 @@ CCS can provision first-class local tools like WebSearch and image analysis for
|
||||
third-party launches instead of leaving you to wire them by hand. Browser
|
||||
automation now has a first-class setup path as well. Deep dive:
|
||||
[WebSearch](https://docs.ccs.kaitran.ca/features/ai/websearch) |
|
||||
[Browser Automation](./docs/browser-automation.md).
|
||||
[Browser Automation](https://docs.ccs.kaitran.ca/features/workflow/browser-automation).
|
||||
|
||||
## Docs Matrix
|
||||
|
||||
@@ -183,7 +184,7 @@ reference material.
|
||||
| Compare OAuth providers, Claude accounts, and API profiles | [Provider Overview](https://docs.ccs.kaitran.ca/providers/concepts/overview) |
|
||||
| Learn the dashboard structure and feature pages | [Dashboard Overview](https://docs.ccs.kaitran.ca/features/dashboard/overview) |
|
||||
| Configure profiles, paths, and environment variables | [Configuration](https://docs.ccs.kaitran.ca/getting-started/configuration) |
|
||||
| Understand browser attach vs Codex browser tooling | [Browser Automation](./docs/browser-automation.md) |
|
||||
| Understand browser attach vs Codex browser tooling | [Browser Automation](https://docs.ccs.kaitran.ca/features/workflow/browser-automation) |
|
||||
| Keep OpenCode aligned with your live CCS setup | [OpenCode Sync Plugin](https://docs.ccs.kaitran.ca/features/workflow/opencode-sync) |
|
||||
| Browse every command and flag | [CLI Commands](https://docs.ccs.kaitran.ca/reference/cli-commands) |
|
||||
| Recover from install, auth, or provider failures | [Troubleshooting](https://docs.ccs.kaitran.ca/reference/troubleshooting) |
|
||||
|
||||
+46
-258
@@ -1,273 +1,61 @@
|
||||
# Browser Automation
|
||||
# Browser Automation Developer Contract
|
||||
|
||||
Last Updated: 2026-05-11
|
||||
User setup and troubleshooting live in the canonical
|
||||
[Browser Automation guide](https://docs.ccs.kaitran.ca/features/workflow/browser-automation).
|
||||
This local file retains security-sensitive runtime invariants that contributors
|
||||
must preserve until the public guide covers them fully.
|
||||
|
||||
CCS provides browser automation through two separate runtime paths:
|
||||
## Exposure And Runtime Ownership
|
||||
|
||||
- **Claude Browser Attach**: reuses a running Chrome/Chromium session through the CCS-managed local `ccs-browser` MCP runtime
|
||||
- **Codex Browser Tools**: injects Playwright MCP tooling into Codex-target launches
|
||||
- Claude Browser Attach and Codex Browser Tools are separate lanes; they do not
|
||||
promise a shared browser session.
|
||||
- New installs and upgrades without saved browser settings default both lanes
|
||||
to `enabled: false` and `policy: manual`.
|
||||
- `--browser` and `--no-browser` are one-run exposure overrides. They do not
|
||||
change saved policy.
|
||||
- CCS owns `mcpServers.ccs-browser`, the
|
||||
`~/.ccs/mcp/ccs-browser-server.cjs` runtime, and Codex `ccs_browser`
|
||||
overrides. Generic MCP editors are not the primary setup surface.
|
||||
|
||||
These are related, but they are not the same implementation and they do not promise a shared browser session.
|
||||
On new installs, and on upgrades that do not already have explicit browser settings, both lanes
|
||||
start **disabled** and **manual** so browser tooling is not auto-exposed until you opt in.
|
||||
|
||||
## How Browser Automation Works
|
||||
|
||||
### Claude Browser Attach
|
||||
|
||||
Claude-target CCS launches can provision a managed local MCP server named `ccs-browser`.
|
||||
That path is designed for workflows where you want Claude to interact with a browser session
|
||||
that already has useful authenticated state.
|
||||
|
||||
Claude Browser Attach requires a browser launched in attach mode with remote debugging
|
||||
enabled. A recent Chrome update alone is not sufficient.
|
||||
|
||||
### Codex Browser Tools
|
||||
|
||||
Codex-target CCS launches use a separate managed path: CCS injects Playwright MCP overrides
|
||||
for the `ccs_browser` runtime config entry.
|
||||
|
||||
This is configured from the same Browser settings surface, but it is distinct from Claude
|
||||
Browser Attach.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Via Dashboard
|
||||
|
||||
Open `ccs config` -> `Settings` -> `Browser`.
|
||||
|
||||
The Browser screen exposes two sections:
|
||||
|
||||
- **Claude Browser Attach**
|
||||
- enable/disable the Claude attach lane
|
||||
- choose the Chrome user-data directory
|
||||
- set the expected DevTools port
|
||||
- review readiness and next-step guidance
|
||||
- copy a generated browser launch command
|
||||
- **Codex Browser Tools**
|
||||
- enable/disable CCS-managed browser tooling for Codex-target launches
|
||||
- review whether the detected Codex build supports managed browser overrides
|
||||
|
||||
Browser policy controls are CLI-first in this release. The dashboard remains the shared setup and
|
||||
status surface, while `ccs browser policy` is the authoritative place to decide whether browser
|
||||
tooling is auto-exposed or kept manual by default. Fresh installs, plus upgrades without an
|
||||
existing browser section, surface both lanes as off/manual until you explicitly enable them.
|
||||
|
||||
### Via CLI
|
||||
|
||||
```bash
|
||||
ccs help browser
|
||||
ccs browser setup
|
||||
ccs browser status
|
||||
ccs browser doctor
|
||||
ccs browser policy
|
||||
ccs browser policy --all manual
|
||||
```
|
||||
|
||||
Use `ccs browser setup` for the primary one-command setup path. Use `ccs browser status` for
|
||||
the current state, `ccs browser doctor` for read-only troubleshooting guidance, and
|
||||
`ccs browser policy` to control default browser exposure. If you only want browser access for one
|
||||
run, keep policy manual and add `--browser` to that launch.
|
||||
|
||||
### Via Config File
|
||||
|
||||
Edit `~/.ccs/config.yaml`:
|
||||
|
||||
```yaml
|
||||
browser:
|
||||
claude:
|
||||
enabled: false
|
||||
policy: manual
|
||||
user_data_dir: "~/.ccs/browser/chrome-user-data"
|
||||
devtools_port: 9222
|
||||
codex:
|
||||
enabled: false
|
||||
policy: manual
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `claude.policy` and `codex.policy` accept `auto` or `manual`
|
||||
- `claude.user_data_dir` is a **Chrome user-data directory**, not a display-name browser profile
|
||||
- `claude.devtools_port` is the expected remote debugging port for attach mode
|
||||
- `codex.enabled` controls whether CCS injects browser tooling into Codex-target launches
|
||||
- New installs, plus upgrades without saved browser settings, default both lanes to `enabled: false` and `policy: manual`
|
||||
- `manual` keeps the lane configured but hidden until a launch explicitly opts in with `--browser`
|
||||
|
||||
## Runtime Policy Controls
|
||||
|
||||
CCS now separates **lane enablement** from **default exposure policy**:
|
||||
|
||||
- `enabled: false`
|
||||
- the lane is off; this is the default for both lanes on new installs and upgrades without saved browser settings
|
||||
- `enabled: true` + `policy: auto`
|
||||
- the lane is exposed automatically on matching launches
|
||||
- `enabled: true` + `policy: manual`
|
||||
- the lane stays configured, but CCS keeps browser tooling hidden unless the current launch uses
|
||||
`--browser`
|
||||
|
||||
One-run launch overrides:
|
||||
|
||||
```bash
|
||||
ccs browser policy --all manual
|
||||
ccs glm --browser "inspect the page"
|
||||
ccs glm --no-browser "summarize the docs"
|
||||
ccs default --target codex --browser "use the browser tools for this run"
|
||||
```
|
||||
|
||||
- `--browser` forces browser tooling on for the current launch when that lane is enabled
|
||||
- `--no-browser` suppresses browser tooling for the current launch even when policy is `auto`
|
||||
|
||||
## Environment Variable Overrides
|
||||
|
||||
CCS still supports environment-variable overrides for backward compatibility.
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `CCS_BROWSER_USER_DATA_DIR` | Preferred override for Claude Browser Attach user-data dir |
|
||||
| `CCS_BROWSER_PROFILE_DIR` | Legacy alias for the same attach directory |
|
||||
| `CCS_BROWSER_DEVTOOLS_PORT` | Explicit DevTools port override |
|
||||
| `CCS_BROWSER_INTERCEPT_FULFILL_MODE=enabled` | Dangerous local-testing opt-in for Browser MCP response fulfillment; disabled by default |
|
||||
| `CCS_BROWSER_UPLOAD_ROOTS` | Optional `path.delimiter`-separated allowlist for local files that browser upload tools may read |
|
||||
| `CCS_BROWSER_DOWNLOAD_ROOTS` | Optional `path.delimiter`-separated allowlist for caller-provided browser download directories |
|
||||
|
||||
If an override is active, Browser status surfaces should report that the current session is being
|
||||
managed externally by environment variables.
|
||||
|
||||
Browser MCP request interception can continue or fail matched requests by default. Synthetic response
|
||||
fulfillment (`action: fulfill`) is more sensitive because it can serve caller-supplied response
|
||||
content inside the attached browser's target origin. CCS therefore hides and blocks fulfillment unless
|
||||
`CCS_BROWSER_INTERCEPT_FULFILL_MODE=enabled` is set for a trusted local test session.
|
||||
|
||||
The saved browser policy still controls default exposure. Env overrides change the effective attach
|
||||
path/port for the current shell; they do not bypass `policy: manual`.
|
||||
|
||||
Override precedence is:
|
||||
## Attach Override Precedence
|
||||
|
||||
1. `CCS_BROWSER_USER_DATA_DIR`
|
||||
2. `CCS_BROWSER_PROFILE_DIR`
|
||||
3. the persisted `browser.claude.user_data_dir` config value
|
||||
2. legacy `CCS_BROWSER_PROFILE_DIR`
|
||||
3. persisted `browser.claude.user_data_dir`
|
||||
|
||||
Config-backed Browser Attach always passes an explicit DevTools port to the runtime, even when the
|
||||
effective value is the default `9222`. Metadata-based port discovery is preserved only for the
|
||||
legacy `CCS_BROWSER_PROFILE_DIR` flow when `CCS_BROWSER_DEVTOOLS_PORT` is not set.
|
||||
Config-backed attach always passes an explicit DevTools port, including the
|
||||
default `9222`. Metadata-based port discovery exists only for the legacy
|
||||
profile-dir override when `CCS_BROWSER_DEVTOOLS_PORT` is unset.
|
||||
|
||||
### Browser File Transfer Safety
|
||||
## Interception Safety
|
||||
|
||||
Claude Browser Attach file-transfer tools intentionally use a deny-by-default filesystem boundary:
|
||||
Request interception may continue or fail matched requests by default.
|
||||
Synthetic fulfillment can serve caller-supplied content inside the target
|
||||
origin, so it must stay hidden and blocked unless
|
||||
`CCS_BROWSER_INTERCEPT_FULFILL_MODE=enabled` is explicitly set for a trusted
|
||||
local test.
|
||||
|
||||
- Downloads without an explicit `downloadPath` go to a CCS-created temporary session directory.
|
||||
- A caller-provided `downloadPath` must be inside that temporary session directory or inside one of
|
||||
the directories listed in `CCS_BROWSER_DOWNLOAD_ROOTS`.
|
||||
- Local upload and drag-and-drop files must be inside the temporary session download directory or
|
||||
inside one of the directories listed in `CCS_BROWSER_UPLOAD_ROOTS`.
|
||||
- Hidden path segments and common secret locations/files, such as `.ssh`, `.aws`, `.ccs`,
|
||||
`.claude`, `.env`, and private-key filenames, are rejected even inside an allowed root.
|
||||
- Each file-transfer call is limited to 10 files, and each local file must be at most 10 MiB.
|
||||
## Event Observation Safety
|
||||
|
||||
Set upload/download roots only to purpose-built scratch directories. Do not point these variables at
|
||||
your home directory, a source checkout with secrets, or a real cloud/tooling config directory.
|
||||
- `browser_wait_for_event` requires `urlIncludes` for network-request events.
|
||||
- Download events require either `urlIncludes` or
|
||||
`suggestedFilenameIncludes`.
|
||||
- Returned navigation, request, and download URLs must redact query strings,
|
||||
fragments, and path-scoped bearer values before reaching the MCP caller.
|
||||
|
||||
## Managed Runtime Files
|
||||
## File Transfer Safety
|
||||
|
||||
- `~/.claude.json` -> CCS manages `mcpServers.ccs-browser` for Claude Browser Attach
|
||||
- `~/.ccs/mcp/ccs-browser-server.cjs` -> local Claude Browser Attach MCP runtime
|
||||
- `Codex runtime config overrides` -> CCS manages the `ccs_browser` MCP entry for Codex-target launches
|
||||
Browser file transfer is deny-by-default:
|
||||
|
||||
Do not treat the generic Codex MCP editor as the primary browser setup path. CCS-managed browser
|
||||
entries should be configured from `Settings -> Browser`.
|
||||
- implicit downloads use a CCS-created temporary session directory;
|
||||
- explicit download paths must remain inside that session directory or a
|
||||
`CCS_BROWSER_DOWNLOAD_ROOTS` allowlisted root;
|
||||
- uploads and drag-and-drop files must remain inside the session directory or a
|
||||
`CCS_BROWSER_UPLOAD_ROOTS` allowlisted root;
|
||||
- hidden segments and common secret locations/files (`.ssh`, `.aws`, `.ccs`,
|
||||
`.claude`, `.env`, private keys) remain denied inside allowed roots;
|
||||
- each call permits at most 10 files, each no larger than 10 MiB.
|
||||
|
||||
## Primary Setup Flow
|
||||
|
||||
The shortest supported setup path is:
|
||||
|
||||
```bash
|
||||
ccs browser setup
|
||||
```
|
||||
|
||||
That flow:
|
||||
|
||||
1. enables Claude Browser Attach in the saved CCS browser config
|
||||
2. leaves launch exposure under the saved policy, so `policy: manual` still requires `--browser`
|
||||
3. keeps the configured DevTools port normalized
|
||||
4. creates the configured browser user-data directory if needed
|
||||
5. prints the exact browser launch command for the current platform
|
||||
6. re-checks readiness and reports the next step if Chrome still needs manual attention
|
||||
|
||||
## Launching Chrome For Claude Attach
|
||||
|
||||
Claude Browser Attach needs a browser launched with remote debugging.
|
||||
|
||||
Typical examples:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
open -na "Google Chrome" --args --remote-debugging-port=9222 --user-data-dir="$HOME/.ccs/browser/chrome-user-data"
|
||||
|
||||
# Linux
|
||||
google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.ccs/browser/chrome-user-data"
|
||||
|
||||
# Windows
|
||||
chrome.exe --remote-debugging-port=9222 --user-data-dir="%USERPROFILE%\\.ccs\\browser\\chrome-user-data"
|
||||
```
|
||||
|
||||
Using a dedicated CCS browser data dir is recommended. It avoids profile-locking issues and keeps
|
||||
automation state separate from your daily browser profile.
|
||||
|
||||
When Claude Browser Attach uses the recommended managed path (`~/.ccs/browser/chrome-user-data`),
|
||||
CCS now creates that directory automatically the first time it needs it. After that bootstrap step,
|
||||
the remaining requirement is a running Chrome session started with `--remote-debugging-port`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Browser status says Claude Browser Attach is disabled
|
||||
|
||||
Run `ccs browser setup`, enable Claude Browser Attach in `Settings -> Browser`, or edit the
|
||||
browser config block in `~/.ccs/config.yaml`.
|
||||
|
||||
### Browser status says the path is missing
|
||||
|
||||
The configured Chrome user-data directory does not exist yet.
|
||||
|
||||
1. Run `ccs browser setup`
|
||||
2. If Chrome still is not ready, use the generated launch command
|
||||
3. Rerun `ccs browser doctor`
|
||||
|
||||
If you are using the CCS-managed default path, this usually means the path could not be created
|
||||
automatically and now needs manual attention.
|
||||
|
||||
### Browser status says no running browser session was found
|
||||
|
||||
CCS could not find usable DevTools attach metadata for the configured user-data directory.
|
||||
|
||||
1. Run `ccs browser setup`
|
||||
2. If needed, make sure Chrome was started with `--remote-debugging-port=<port>`
|
||||
3. Make sure it is using the same `user_data_dir` configured in CCS
|
||||
4. Rerun `ccs browser doctor`
|
||||
|
||||
For the CCS-managed default path, this is the normal first-run state after CCS bootstraps the
|
||||
directory for you.
|
||||
|
||||
### Browser status says the DevTools endpoint is unreachable
|
||||
|
||||
CCS found attach metadata, but the endpoint did not answer successfully.
|
||||
|
||||
1. Run `ccs browser setup`
|
||||
2. If needed, restart the attach browser session
|
||||
3. Confirm the expected port matches the real remote debugging port
|
||||
4. Rerun `ccs browser status`
|
||||
|
||||
### Codex Browser Tools are unavailable
|
||||
|
||||
Codex browser tooling depends on a Codex build that supports `--config` overrides.
|
||||
|
||||
If CCS reports `unsupported_build`, upgrade Codex and rerun `ccs browser status`.
|
||||
|
||||
## Security Notes
|
||||
|
||||
- Browser automation may operate inside authenticated browser sessions
|
||||
- Prefer a dedicated automation user-data dir instead of your everyday browser profile
|
||||
- Do not commit browser paths, secrets, or generated session state to version control
|
||||
- Treat `~/.ccs/config.yaml`, `~/.claude.json`, and the browser user-data directory as local machine state
|
||||
- `browser_wait_for_event` requires explicit scoping for network request events (`urlIncludes`) and download events (`urlIncludes` or `suggestedFilenameIncludes`)
|
||||
- Event details redact observed navigation, request, and download URLs before returning them to the MCP caller so observed browser metadata does not expose query strings, fragments, or path-scoped bearer values
|
||||
Allowlist only purpose-built scratch directories. Never allowlist a home
|
||||
directory, source checkout containing secrets, or real tooling configuration
|
||||
directory.
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
# CCS Bar — Native macOS Menu Bar App
|
||||
|
||||
CCS Bar is a native macOS menu-bar app that shows live subscription quota and usage at a glance for your Claude Code, Codex, and CLIProxy accounts, without opening the dashboard.
|
||||
|
||||
## What It Is
|
||||
|
||||
CCS Bar is a thin client of the CCS local web-server. It never talks to a provider directly: every call goes to `localhost`, and CCS performs any provider fetch server-side. Opening the menu fires a debounced force-refresh so the glance reflects live data without blocking the UI.
|
||||
|
||||
It is macOS only.
|
||||
|
||||
## What It Shows
|
||||
|
||||
- Per-account quota percent and reset countdown
|
||||
- Account tier
|
||||
- Today, 7-day, and 30-day cost
|
||||
- A 30-day usage sparkline
|
||||
- Account state (active, paused, default)
|
||||
- Native subscription rows for Claude Code and Codex
|
||||
|
||||
## Requirements
|
||||
|
||||
- macOS
|
||||
- CCS CLI installed and configured (`ccs config` works)
|
||||
- The CCS web-server reachable on loopback. `ccs bar` (or `ccs bar launch`) starts it for you.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
ccs bar install
|
||||
```
|
||||
|
||||
This downloads `CCS-Bar.app.zip` from the floating `ccs-bar-latest` GitHub release and installs `CCS Bar.app` into `~/Applications`. Downloads are restricted to `github.com` and `objects.githubusercontent.com`, and extraction is guarded against zip-slip.
|
||||
|
||||
If `CCS Bar.app` is already installed, the command shows the current version and proceeds as a reinstall.
|
||||
|
||||
After installation, CCS reads the app version directly from the bundle's `Info.plist` and pins it to `~/.ccs/bar/.version`. It then performs a reachability check against the bar API (`GET /api/bar/summary`). A 404 response means the running CCS server predates CCS Bar support — update CCS to a version that includes CCS Bar, then restart `ccs bar`.
|
||||
|
||||
After a successful install, CCS leaves the macOS Gatekeeper quarantine marker in place so macOS can perform its normal first-launch verification. CCS then asks whether to launch CCS Bar immediately (default: yes). Pass `--launch` to skip the prompt and launch right away, or `--no-launch` to suppress the prompt entirely:
|
||||
|
||||
```bash
|
||||
ccs bar install --launch # install and launch immediately
|
||||
ccs bar install --no-launch # install, skip launch prompt
|
||||
```
|
||||
|
||||
### Gatekeeper note
|
||||
|
||||
The install command does not automatically clear the macOS Gatekeeper quarantine attribute on the downloaded app. This preserves macOS first-launch verification for the floating release download.
|
||||
|
||||
If macOS blocks the app on first launch, make an explicit trust decision by right-clicking the app and choosing Open.
|
||||
|
||||
## Run
|
||||
|
||||
There are two ways to run CCS Bar, and both share one background server.
|
||||
|
||||
**Open the app.** Launch `CCS Bar` from Spotlight, Finder, or the Dock. The first time you open the menu, the app looks for a running CCS server and, if it does not find one, starts it in the background using the launch details recorded at install (`~/.ccs/bar/launch.json`). No terminal needed.
|
||||
|
||||
**Or use the CLI.**
|
||||
|
||||
```bash
|
||||
ccs bar # alias: ccs bar launch
|
||||
```
|
||||
|
||||
`ccs bar` probes for a running server (ports 3000, 3001, 3002, 8000, 8080, with the previous `bar.json` port first). If one is live it is reused; otherwise `ccs bar` starts the server as a detached background process and returns your prompt right away, then opens the app. The server keeps running after the command exits, so closing the terminal does not take the bar offline.
|
||||
|
||||
### Server lifecycle
|
||||
|
||||
```bash
|
||||
ccs bar serve # run the server in the current terminal (advanced; ccs bar runs this detached for you)
|
||||
ccs bar stop # stop the background server
|
||||
ccs bar status # report whether the server is running
|
||||
```
|
||||
|
||||
### Files under ~/.ccs
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `~/.ccs/bar.json` | Discovery file the app reads to find the live server |
|
||||
| `~/.ccs/bar/launch.json` | How to start the server (recorded at install) so the app can start it without a shell PATH |
|
||||
| `~/.ccs/bar/server.pid` | PID of the background server, used by `ccs bar stop` and `ccs bar status` |
|
||||
| `~/.ccs/bar/serve.log` | stdout and stderr of the background server |
|
||||
|
||||
`~/.ccs/bar.json` looks like this:
|
||||
|
||||
```json
|
||||
{ "baseUrl": "http://127.0.0.1:3000", "port": 3000, "authMode": "loopback" }
|
||||
```
|
||||
|
||||
The Swift app reads `~/.ccs/bar.json` to find the server, and falls back to probing the candidate ports if it is missing or stale.
|
||||
|
||||
## Loopback / Localhost Requirement
|
||||
|
||||
CCS Bar talks only to `http://127.0.0.1:<port>`. v1 supports `authMode: "loopback"` only, meaning dashboard auth disabled on localhost.
|
||||
|
||||
If you bind the dashboard beyond localhost (for example `--host` set to a non-loopback address) with dashboard auth disabled, the bar's read endpoints (`GET /api/bar/summary`, `GET /api/bar/analytics`) are refused for non-loopback callers, and the app cannot reach the server. Keep the dashboard on loopback for CCS Bar to work.
|
||||
|
||||
## Uninstall
|
||||
|
||||
```bash
|
||||
ccs bar uninstall
|
||||
```
|
||||
|
||||
This removes `~/Applications/CCS Bar.app` and the installed version pin. It is a no-op if the app is not present.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- Install fails with "server predates CCS Bar" or bar API returns 404: the CCS server running does not yet include CCS Bar. Update CCS (`npm i -g ccs@latest` or equivalent), then restart `ccs bar`.
|
||||
- Server failed to start: `ccs bar` first checks whether a CCS server is already running on the candidate ports (3000, 3001, 3002, 8000, 8080) and reuses it if found. A true failure here means a non-CCS process is occupying all candidate ports. Free one of those ports and re-run `ccs bar`. Check `~/.ccs/bar/serve.log` for the background server's output.
|
||||
- App won't open (Gatekeeper): right-click the app and choose Open to make an explicit trust decision.
|
||||
- Menu shows "CCS is not running": open the menu again to let the app start the server, or run `ccs bar status` to check and `ccs bar` to start it.
|
||||
- Quota not updating: re-open the menu to force a refresh, or confirm the server is still reachable on loopback.
|
||||
|
||||
## Development
|
||||
|
||||
The source lives in `macos-bar/`. Contributors can build and run the logic checks with a Swift 5.9+ toolchain (CommandLineTools is enough, full Xcode not required):
|
||||
|
||||
```bash
|
||||
swift build # build all targets, including the app
|
||||
swift run ccs-bar-check # run the logic tests
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
The app ships as a single floating GitHub release asset, `CCS-Bar.app.zip` under the `ccs-bar-latest` tag, which is what `ccs bar install` downloads. The version comes from one file: `macos-bar/VERSION` (a single line of semver). Bump it in the same PR when you want a new number; the asset is always the latest build regardless.
|
||||
|
||||
### Automatic (preferred)
|
||||
|
||||
The `Bar Release` workflow (`.github/workflows/bar-release.yml`) builds and publishes the asset automatically. It is scoped tightly so it never affects other PRs or CI:
|
||||
|
||||
- It runs only on a push to `main` that touches `macos-bar/**`, or a manual run from the Actions tab (`workflow_dispatch`) when the selected ref is `main`.
|
||||
- It runs only on the dedicated self-hosted macOS runner (label `ccs-bar`); the Linux CI runners never pick it up and it never competes for them.
|
||||
|
||||
So bar changes reach users when they land on `main` (the stable cadence). To cut a release without a code change, or to re-publish, trigger the workflow manually with `main` selected as the workflow ref.
|
||||
|
||||
### Manual fallback
|
||||
|
||||
From a macOS machine with the Swift toolchain and `gh`:
|
||||
|
||||
```bash
|
||||
cd macos-bar
|
||||
./Scripts/package_app.sh # uses macos-bar/VERSION; pass a version to override
|
||||
gh release upload ccs-bar-latest dist/CCS-Bar.app.zip --clobber
|
||||
```
|
||||
|
||||
Builds are ad-hoc signed by default. Developer ID notarization (for the no-prompt public install) is available via `CCS_BAR_SIGNING=developer-id` once a paid Apple cert is configured.
|
||||
+58
-202
@@ -1,217 +1,73 @@
|
||||
# Codex Auth Profile Isolation (`ccsx auth`)
|
||||
# Codex Auth Developer Contract
|
||||
|
||||
Run two Codex accounts simultaneously — one per terminal — with full auth isolation.
|
||||
The canonical [Codex Adapter guide](https://docs.ccs.kaitran.ca/features/workflow/codex-adapter)
|
||||
owns user setup. This local contract documents active `ccsx auth` invariants
|
||||
that contributors and operators must preserve.
|
||||
|
||||
## Why
|
||||
## Command Surface
|
||||
|
||||
Codex stores its OAuth credentials in a single directory (`~/.codex/`). When you run two
|
||||
`codex` sessions in separate terminals, they both write to the same `auth.json`. A token
|
||||
refresh in one session overwrites the other's credentials.
|
||||
`ccsx auth` owns `create`, `login`, `switch`, `use`, `show`, `remove`, and
|
||||
`import-default`. Keep syntax and option changes sourced from
|
||||
[`src/codex-auth/codex-auth-help.ts`](../src/codex-auth/codex-auth-help.ts)
|
||||
rather than copying a long command reference here.
|
||||
|
||||
`ccsx auth` solves this by giving each account its own profile directory under
|
||||
`~/.ccs/codex-instances/<name>/`. Each profile holds its own `auth.json` and
|
||||
`history.jsonl`, plus its own session data. Shared `config.toml`, `agents/`, `skills/`,
|
||||
and plugin cache resources come from `~/.codex/` so configuration and installed plugin
|
||||
skills stay in sync across profiles.
|
||||
- `create <name>` is idempotent and starts native `codex login` for new
|
||||
profiles. `--force` repairs shared resources without replacing `auth.json`.
|
||||
- `switch <name>` changes the persistent registry default.
|
||||
- `use <name>` emits only shell-evaluable `CODEX_HOME` and
|
||||
`CCS_CODEX_PROFILE` assignments to stdout. It affects the current shell after
|
||||
`eval`/`source`; diagnostics stay on stderr.
|
||||
- `ccsx <name>` launches a named profile directly without changing the
|
||||
persistent default.
|
||||
|
||||
## Quick start (4 commands)
|
||||
## Import Safety
|
||||
|
||||
```bash
|
||||
# Create and authenticate two profiles
|
||||
ccsx auth create work # creates ~/.ccs/codex-instances/work/ and prompts for login
|
||||
ccsx auth create personal # same for personal account
|
||||
`import-default <name>` imports native `~/.codex/auth.json` without deleting the
|
||||
source. The implementation in
|
||||
[`import-default-command.ts`](../src/codex-auth/commands/import-default-command.ts)
|
||||
must continue to:
|
||||
|
||||
# Activate per terminal (ephemeral — only this shell)
|
||||
# Terminal A:
|
||||
eval "$(ccsx auth use work)"
|
||||
codex
|
||||
- refuse import while a current-user Codex process may be refreshing tokens,
|
||||
unless the operator explicitly accepts the race with
|
||||
`--force-while-running`;
|
||||
- retry and validate JSON/JWT shape, reject CLIProxy auth-file formats, and fail
|
||||
without registering a profile when a torn write persists;
|
||||
- write the destination atomically with private permissions;
|
||||
- omit history and sessions unless `--with-history` is requested;
|
||||
- refuse an existing profile unless `--force` is used, and preserve its current
|
||||
`auth.json` as `auth.json.bak-<timestamp>` before overwrite.
|
||||
|
||||
# Terminal B:
|
||||
eval "$(ccsx auth use personal)"
|
||||
codex
|
||||
## Storage And Cross-Platform Fallback
|
||||
|
||||
# Or launch a named profile directly through ccsx
|
||||
ccsx work
|
||||
```
|
||||
|
||||
## Two-terminal example
|
||||
|
||||
```bash
|
||||
# Terminal A — work account
|
||||
eval "$(ccsx auth use work)"
|
||||
codex # runs with CODEX_HOME=~/.ccs/codex-instances/work
|
||||
|
||||
# Terminal B — personal account (simultaneously)
|
||||
eval "$(ccsx auth use personal)"
|
||||
codex # runs with CODEX_HOME=~/.ccs/codex-instances/personal
|
||||
|
||||
# No token clobbering. Each session refreshes its own auth.json only.
|
||||
```
|
||||
|
||||
## Command reference
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `ccsx auth create <name>` | Create profile dir + auto-login |
|
||||
| `ccsx <name>` | Launch a named Codex auth profile |
|
||||
| `ccsx auth login <name>` | (Re-)authenticate an existing profile |
|
||||
| `ccsx auth switch <name>` | Set the persistent default profile for future `ccsx` launches |
|
||||
| `ccsx auth use <name>` | Emit shell exports for this shell only (use with `eval`) |
|
||||
| `ccsx auth show [name]` | List all profiles or show details for one |
|
||||
| `ccsx auth remove <name>` | Delete profile dir + registry entry |
|
||||
| `ccsx auth import-default <name>` | Migrate legacy `~/.codex/auth.json` into a new profile |
|
||||
|
||||
## Persistent vs ephemeral switching
|
||||
|
||||
| Method | Scope | How |
|
||||
|--------|-------|-----|
|
||||
| `ccsx <name>` | One launch | Resolves `<name>` from the Codex profile registry |
|
||||
| `ccsx auth switch <name>` | Future `ccsx` launches | Writes to `~/.ccs/codex-profiles.yaml` |
|
||||
| `eval "$(ccsx auth use <name>)"` | Current shell only | Sets `CODEX_HOME` + `CCS_CODEX_PROFILE` in your shell |
|
||||
|
||||
Native `codex` shells only see the persistent default when launched through the `ccsx`
|
||||
Codex runtime. For an already-open shell or a plain native `codex` binary, use `auth use`.
|
||||
|
||||
Do not use `ccs persist codex` for Claude Code or the Claude Code Extension. That path
|
||||
would persist Claude settings that send Claude traffic through the Codex translator. CCS
|
||||
blocks Codex CLIProxy profiles from Claude extension setup; use `ccsxp` or
|
||||
`ccs codex --target codex` for ChatGPT/Codex subscriptions. If old settings were already
|
||||
persisted, clear them with:
|
||||
|
||||
```bash
|
||||
ccs persist default --yes
|
||||
```
|
||||
|
||||
The command prints a config receipt after writing settings: cleared managed keys,
|
||||
written managed keys, whether and where any `/api/provider/codex` translator URL
|
||||
remains, and the native Codex targets to use next.
|
||||
|
||||
Shell syntax for `use`:
|
||||
|
||||
```bash
|
||||
# bash / zsh
|
||||
eval "$(ccsx auth use work)"
|
||||
|
||||
# fish
|
||||
ccsx auth use work | source
|
||||
|
||||
# PowerShell
|
||||
ccsx auth use work | Invoke-Expression
|
||||
```
|
||||
|
||||
## Migration from `~/.codex`
|
||||
|
||||
If you already have a logged-in session in `~/.codex/auth.json`, import it without
|
||||
disturbing the original:
|
||||
|
||||
```bash
|
||||
# Auth only (default — recommended)
|
||||
ccsx auth import-default legacy
|
||||
|
||||
# Auth + history + sessions (opt-in)
|
||||
ccsx auth import-default legacy --with-history
|
||||
|
||||
# Make it the default
|
||||
ccsx auth switch legacy
|
||||
```
|
||||
|
||||
The source `~/.codex/` directory is **never modified**. If `import-default` is not run,
|
||||
`codex` continues to work exactly as before.
|
||||
|
||||
### Torn-write safety
|
||||
|
||||
Codex writes `auth.json` with truncate+write (not atomic rename). Running
|
||||
`import-default` while a token refresh is in flight can produce a corrupt copy.
|
||||
The command detects a running `codex` process via `pgrep` and refuses unless you
|
||||
pass `--force-while-running`. The safest approach is to quit Codex before
|
||||
importing.
|
||||
|
||||
## Dashboard
|
||||
|
||||
The CCS dashboard shows active profile metadata at the **Auth Profiles** tab on the
|
||||
Codex page:
|
||||
|
||||
- Profile name and whether it is the current default
|
||||
- Decoded email address (from `id_token` — no signature verification; display only)
|
||||
- Plan tier (Plus, Pro, Free) when present in the token
|
||||
- Last-used timestamp
|
||||
|
||||
No OAuth tokens are ever returned by the API endpoint or shown in the UI.
|
||||
|
||||
## Profile disk layout
|
||||
|
||||
```
|
||||
```text
|
||||
~/.ccs/
|
||||
├── codex-profiles.yaml # Registry: version, default, profiles metadata
|
||||
└── codex-instances/
|
||||
└── <name>/
|
||||
├── auth.json # OAuth credentials (Codex writes here)
|
||||
├── history.jsonl # Per-profile prompt history (optional)
|
||||
├── sessions/ # Per-profile chat session dirs (optional)
|
||||
├── config.toml -> ~/.codex/config.toml (symlink — shared)
|
||||
├── agents/ -> ~/.codex/agents/ (symlink — shared)
|
||||
├── skills/ -> ~/.codex/skills/ (symlink — shared)
|
||||
└── plugins/ # Profile-local parent; may hold local metadata
|
||||
└── cache/ -> ~/.codex/plugins/cache/ (symlink — shared)
|
||||
|
||||
~/.codex/
|
||||
├── config.toml # Single shared model/provider config
|
||||
├── agents/ # Shared Codex agent role config files
|
||||
├── skills/ # Shared Codex skills
|
||||
└── plugins/
|
||||
└── cache/ # Shared installed plugin payloads
|
||||
├── codex-profiles.yaml
|
||||
└── codex-instances/<name>/
|
||||
├── auth.json, history.jsonl, sessions/ # profile-local
|
||||
├── config.toml -> ~/.codex/config.toml
|
||||
├── agents/ -> ~/.codex/agents/
|
||||
├── skills/ -> ~/.codex/skills/
|
||||
└── plugins/
|
||||
└── cache/ -> ~/.codex/plugins/cache/
|
||||
```
|
||||
|
||||
Only `plugins/cache/` is shared. The profile's parent `plugins/` directory remains a
|
||||
real local directory so Codex can keep profile-specific plugin metadata beside the
|
||||
shared cache.
|
||||
The `plugins/` parent stays profile-local. Shared config, resources, and plugin
|
||||
cache repair must preserve existing profile-local content. On Windows or other
|
||||
systems where symlinks are unavailable, CCS copies missing shared content into
|
||||
the profile and warns that later upstream edits will not propagate
|
||||
automatically. See
|
||||
[`codex-config-symlink.ts`](../src/codex-auth/codex-config-symlink.ts),
|
||||
[`codex-profile-resources.ts`](../src/codex-auth/codex-profile-resources.ts),
|
||||
and
|
||||
[`codex-profile-plugin-cache.ts`](../src/codex-auth/codex-profile-plugin-cache.ts).
|
||||
|
||||
`ccsx auth create <name>` and direct `ccsx <name>` launches repair these links
|
||||
idempotently before Codex starts. This keeps relative entries such as
|
||||
`agents/foo.toml` valid and prevents stale first-launch skill warnings after a plugin
|
||||
install or update changes the cache.
|
||||
## `ccsx` And `ccsxp` Isolation
|
||||
|
||||
## Caveats
|
||||
`ccsx auth` applies only to native Codex profiles. `ccsxp` ignores
|
||||
`CCS_CODEX_PROFILE`, uses native `~/.codex` history by default, and routes
|
||||
through its separate CLIProxy Codex pool. `CCSXP_CODEX_HOME` is its explicit
|
||||
home override. Never make a `ccsx auth switch` silently redirect `ccsxp`, merge
|
||||
their auth stores, or consume `ccsx` import backups as pool credentials.
|
||||
|
||||
### Windows symlinks
|
||||
|
||||
On Windows, creating symlinks requires Developer Mode or elevated privileges.
|
||||
If symlink creation fails, CCS falls back to copying `config.toml`, `agents/`,
|
||||
`skills/`, and the current `plugins/cache/` snapshot. Copies do not update live with
|
||||
`~/.codex/`; after a plugin update, another profile launch or
|
||||
`ccsx auth create <name> --force` repair copies newly missing cache entries. Existing
|
||||
profile-local cache files are preserved.
|
||||
|
||||
### Native Codex project-local config warnings
|
||||
|
||||
`ccsx` preserves your current working directory. If you launch from your home directory,
|
||||
native Codex can also see `~/.codex/config.toml` as `./.codex/config.toml`, a
|
||||
project-local config file. Codex rejects user-level-only keys such as `model_providers`
|
||||
and `notify` in project-local config. That warning comes from native Codex config
|
||||
layering, not from the `ccsx auth` profile resource links. Launch from a project
|
||||
directory or move project-local Codex config out of `$HOME/.codex/config.toml` if the
|
||||
warning is noisy.
|
||||
|
||||
### `ccsx` vs `ccsxp`
|
||||
|
||||
`ccsx auth` profiles apply only to the **native `codex`** CLI. They have no effect on
|
||||
`ccsxp` (the CLIProxy round-robin pool). `ccsxp` unconditionally sets its own
|
||||
`CODEX_HOME` on startup and ignores `CCS_CODEX_PROFILE`.
|
||||
|
||||
If you run `eval "$(ccsx auth use work)"` and then invoke `ccsxp`, a notice is emitted
|
||||
to stderr:
|
||||
|
||||
```
|
||||
[i] CCS_CODEX_PROFILE is ignored by ccsxp; profile applies to native 'codex' only
|
||||
```
|
||||
|
||||
### cmd.exe
|
||||
|
||||
`ccsx auth use` emits `set FOO=bar` syntax for cmd.exe. Native `eval` is not available
|
||||
in legacy cmd — use PowerShell (`Invoke-Expression`) instead.
|
||||
|
||||
### Backup files from `--force`
|
||||
|
||||
When re-importing with `--force`, the existing `auth.json` is backed up as
|
||||
`auth.json.bak-<timestamp>` in the profile directory. These accumulate over time; remove
|
||||
them manually when no longer needed.
|
||||
Behavior locks live under `tests/unit/codex-auth/` and
|
||||
`tests/integration/codex-auth/`.
|
||||
@@ -1,165 +0,0 @@
|
||||
# Cursor IDE Integration
|
||||
|
||||
This guide covers the deprecated CCS-owned Cursor IDE bridge, including auth import, local daemon lifecycle, live probe checks, and dashboard controls.
|
||||
|
||||
`ccs cursor` now belongs to the CLIProxy-backed Cursor provider path.
|
||||
Use `ccs legacy cursor` for the deprecated local bridge documented here.
|
||||
|
||||
## What It Provides
|
||||
|
||||
- OpenAI-compatible local endpoint powered by Cursor credentials.
|
||||
- Anthropic-compatible local endpoint at `/v1/messages` for Claude-native clients.
|
||||
- Cursor model list and chat completions via the local CCS daemon.
|
||||
- Dedicated dashboard page: `ccs config` -> `Deprecated` -> `Cursor IDE (Legacy)`.
|
||||
|
||||
## What This Runtime Actually Does
|
||||
|
||||
`ccs legacy cursor` does not launch Cursor IDE itself.
|
||||
|
||||
The current workflow is:
|
||||
1. import Cursor credentials from local SQLite or manual input
|
||||
2. run a local CCS daemon on `127.0.0.1:<port>`
|
||||
3. launch Claude Code against that daemon
|
||||
4. have CCS translate requests to Cursor upstream
|
||||
|
||||
Treat this as a CCS-managed Cursor bridge, not a generic CLIProxy-backed provider path.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Cursor IDE installed and logged in.
|
||||
- CCS installed and configured (`ccs config` works).
|
||||
- For auto-detect auth on macOS/Linux: `sqlite3` available in PATH.
|
||||
|
||||
## CLI Workflow
|
||||
|
||||
### 1) Enable integration
|
||||
|
||||
```bash
|
||||
ccs legacy cursor enable
|
||||
```
|
||||
|
||||
### 2) Import credentials
|
||||
|
||||
Auto-detect from Cursor local SQLite state:
|
||||
|
||||
```bash
|
||||
ccs legacy cursor auth
|
||||
```
|
||||
|
||||
Manual fallback:
|
||||
|
||||
```bash
|
||||
ccs legacy cursor auth --manual --token <token> --machine-id <machine-id>
|
||||
```
|
||||
|
||||
### 3) Start daemon
|
||||
|
||||
```bash
|
||||
ccs legacy cursor start
|
||||
```
|
||||
|
||||
### 4) Run a live probe
|
||||
|
||||
```bash
|
||||
ccs legacy cursor probe
|
||||
```
|
||||
|
||||
Use this to verify that the current build can complete one real authenticated request through the local daemon.
|
||||
|
||||
### 5) Run Cursor-backed Claude
|
||||
|
||||
```bash
|
||||
ccs legacy cursor "explain this repo"
|
||||
```
|
||||
|
||||
### 6) Verify status
|
||||
|
||||
```bash
|
||||
ccs legacy cursor status
|
||||
```
|
||||
|
||||
Use `ccs legacy cursor` with bare or normal Claude args to run through the local Cursor proxy.
|
||||
The admin namespace remains available for setup and inspection:
|
||||
|
||||
```bash
|
||||
ccs legacy cursor help
|
||||
```
|
||||
|
||||
### 7) Stop daemon
|
||||
|
||||
```bash
|
||||
ccs legacy cursor stop
|
||||
```
|
||||
|
||||
## Supported Cursor Provider Path
|
||||
|
||||
For the supported CLIProxy-backed Cursor provider, use:
|
||||
|
||||
```bash
|
||||
ccs cursor --auth
|
||||
ccs cursor --accounts
|
||||
ccs cursor --config
|
||||
ccs cursor "task"
|
||||
```
|
||||
|
||||
## Runtime Defaults
|
||||
|
||||
- Default port: `20129`
|
||||
- `ghost_mode`: enabled
|
||||
- `auto_start`: disabled
|
||||
- Model list resolution: authenticated live fetch when available, with cached/default fallback.
|
||||
- Request model validation: if a requested model is not present in the available Cursor model catalog, daemon falls back to the resolved default model.
|
||||
- Daemon API surface: `POST /v1/chat/completions`, `POST /v1/messages`, and `GET /v1/models`.
|
||||
- Live verification: `ccs legacy cursor probe` or `POST /api/cursor/probe`
|
||||
|
||||
These values are managed in unified config and can be updated from CLI or dashboard.
|
||||
|
||||
## Dashboard Usage
|
||||
|
||||
Open dashboard:
|
||||
|
||||
```bash
|
||||
ccs config
|
||||
```
|
||||
|
||||
Then navigate to `Cursor IDE (Legacy)` in the `Deprecated` section.
|
||||
|
||||
Available controls:
|
||||
|
||||
- Integration toggle (`enabled`)
|
||||
- Auth actions (auto-detect, manual import)
|
||||
- Daemon actions (start/stop)
|
||||
- Runtime config (port, auto-start, ghost mode)
|
||||
- Models list with searchable combobox filtering for large catalogs
|
||||
- Raw editor for `~/.ccs/cursor.settings.json`
|
||||
|
||||
## Raw Settings and Unified Config Sync
|
||||
|
||||
Raw settings are stored in:
|
||||
|
||||
`~/.ccs/cursor.settings.json`
|
||||
|
||||
When raw settings include a local `ANTHROPIC_BASE_URL` port override, CCS synchronizes that port back into unified config so CLI and dashboard remain consistent.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### `Not authenticated` or `expired` in `ccs cursor status`
|
||||
|
||||
- Re-run `ccs legacy cursor auth` (or manual auth command).
|
||||
|
||||
### `ccs legacy cursor probe` fails even though status is green
|
||||
|
||||
- `status` proves local config/auth/daemon readiness only.
|
||||
- `probe` proves the live runtime path.
|
||||
- If `probe` fails with upstream protocol errors, inspect the current CCS build first rather than assuming the local daemon is healthy.
|
||||
|
||||
### Auto-detect fails
|
||||
|
||||
- Ensure Cursor is logged in.
|
||||
- Confirm `sqlite3` is installed or use manual import.
|
||||
- Use manual auth import if needed.
|
||||
|
||||
### Daemon fails to start
|
||||
|
||||
- Check if port `20129` is in use.
|
||||
- Change port in dashboard config tab, then retry `ccs legacy cursor start`.
|
||||
@@ -1,267 +0,0 @@
|
||||
# Dashboard Authentication CLI
|
||||
|
||||
Last Updated: 2026-05-05
|
||||
|
||||
CLI commands for managing CCS dashboard authentication.
|
||||
|
||||
## Overview
|
||||
|
||||
The CCS dashboard (`ccs config`) can be protected with username/password authentication. This is useful whenever the dashboard is reachable from another device, such as when you explicitly bind it beyond loopback with `ccs config --host 0.0.0.0`.
|
||||
|
||||
Authentication is **disabled by default** for backward compatibility, while `ccs config` binds to `localhost` by default. Use the CLI to configure and enable auth before exposing the dashboard to other devices.
|
||||
|
||||
CCS does **not** ship a default dashboard username or password. When someone opens the dashboard from a non-loopback/IP address before auth is enabled, the UI now shows a setup state instead of an ambiguous login form. The host owner must run `ccs config auth setup`, or the user should switch back to the localhost URL if they are on the same machine.
|
||||
|
||||
Docker note: the integrated `ccs docker` stack stores its config inside the running container volume, not in the outer shell's `~/.ccs`. For Docker deployments, run auth setup inside the container:
|
||||
|
||||
```bash
|
||||
docker exec -it ccs-cliproxy ccs config auth setup
|
||||
```
|
||||
|
||||
When auth stays disabled, CCS now applies a localhost-only fallback on sensitive management endpoints. Remote devices can still open the dashboard UI when you intentionally bind it beyond loopback, but write-capable routes such as AI Provider management and CLIProxy auth/status helpers reject non-loopback requests until you enable dashboard auth.
|
||||
|
||||
## Account Context Modes (Related Feature)
|
||||
|
||||
Dashboard auth and account context metadata are separate:
|
||||
|
||||
- `dashboard_auth`: protects dashboard access with username/password
|
||||
- `accounts.<name>.context_mode/context_group`: controls isolated vs shared account context
|
||||
- `accounts.<name>.shared_resource_mode`: controls plugins/commands/skills/agents/settings.json sharing
|
||||
|
||||
Account context is isolation-first. The recommended two-account route is:
|
||||
|
||||
```bash
|
||||
ccs auth create work
|
||||
ccs auth create personal
|
||||
ccs work
|
||||
ccs personal
|
||||
```
|
||||
|
||||
Only enable history sync when both accounts should share local continuity while tokens stay separate:
|
||||
|
||||
| Mode | Default | Requirement |
|
||||
|------|---------|-------------|
|
||||
| `isolated` | Yes | No `context_group` required |
|
||||
| `shared` | No (opt-in) | Valid non-empty `context_group` |
|
||||
|
||||
Shared continuity depth:
|
||||
|
||||
- `standard` (default): shares project workspace context only
|
||||
- `deeper` (advanced opt-in): also syncs `session-env`, `file-history`, `shell-snapshots`, `todos`
|
||||
|
||||
`ccs auth show <profile>` reports credential isolation, shared resource mode, settings sync state, history lane, and whether plain `ccs` currently uses the same resume lane.
|
||||
|
||||
Non-bare account profiles share Claude-local resources with native Claude:
|
||||
|
||||
```text
|
||||
~/.ccs/instances/<profile>/settings.json -> ~/.ccs/shared/settings.json -> ~/.claude/settings.json
|
||||
```
|
||||
|
||||
This keeps ordinary Claude settings, plugins, commands, skills, and agents in sync without copying account tokens. Existing accounts can opt out or back in:
|
||||
|
||||
```bash
|
||||
ccs auth resources work --mode profile-local
|
||||
ccs auth resources work --mode shared
|
||||
```
|
||||
|
||||
Local history is separate: if users want future plain `ccs` and `ccs ck` sessions to resume from the same account lane, run `ccs auth default ck` after backing up the current native lane with `ccs auth backup default`.
|
||||
|
||||
`context_group` normalization and validation:
|
||||
|
||||
- trim + lowercase + collapse internal whitespace to `-`
|
||||
- allowed characters: lowercase letters, numbers, `_`, `-`
|
||||
- must start with a letter
|
||||
- max length: 64
|
||||
- shared mode requires non-empty value after normalization
|
||||
- `continuity_mode` is only valid when mode is `shared`
|
||||
|
||||
`PUT /api/config` behavior for account context:
|
||||
|
||||
- rejects invalid unified payloads
|
||||
- rejects explicit `context_mode: shared` with invalid/empty `context_group`
|
||||
- rejects invalid `continuity_mode` values
|
||||
- normalizes valid shared `context_group` before save
|
||||
- defaults missing shared `continuity_mode` to `standard`
|
||||
- rejects `context_group` when mode is not `shared`
|
||||
- rejects `continuity_mode` when mode is not `shared`
|
||||
|
||||
Dashboard accounts context editing:
|
||||
|
||||
- `PUT /api/accounts/:name/context` updates context mode/group/continuity for existing auth accounts
|
||||
- rejects CLIProxy OAuth account keys for this route
|
||||
- applies normalization/validation rules above
|
||||
|
||||
Shared resource editing:
|
||||
|
||||
- `PUT /api/accounts/:name/shared-resources` updates `shared_resource_mode` for existing auth accounts
|
||||
- accepts only `shared` or `profile-local`
|
||||
- rejects CLIProxy OAuth account keys for this route
|
||||
- reconciles the account instance after metadata is updated
|
||||
- Dashboard -> Accounts exposes this as a separate Resources action so it is not confused with History Sync.
|
||||
- Dashboard -> Shared Resources shows the shared hub inventory for commands, skills, agents, plugins, and `settings.json`.
|
||||
- The Plugins tab is registry-oriented: installed plugin entries come from `installed_plugins.json`, while internal cache/data/marketplace folders stay hidden unless a real plugin entry exists.
|
||||
- Shared `settings.json` is read-only in the Shared Resources page and still edited through the settings surfaces that own those values.
|
||||
|
||||
## Commands
|
||||
|
||||
### `ccs config auth setup`
|
||||
|
||||
Interactive wizard to configure dashboard login.
|
||||
|
||||
```bash
|
||||
$ ccs config auth setup
|
||||
|
||||
╭─────────────────────────────────╮
|
||||
│ Dashboard Auth Setup │
|
||||
╰─────────────────────────────────╯
|
||||
|
||||
[i] Configure username and password for dashboard access.
|
||||
Password will be hashed with bcrypt before storage.
|
||||
|
||||
Username
|
||||
Enter username: admin
|
||||
|
||||
Password
|
||||
Minimum 8 characters
|
||||
Enter password: ********
|
||||
Confirm password: ********
|
||||
|
||||
[i] Hashing password...
|
||||
|
||||
[OK] Dashboard authentication configured
|
||||
|
||||
[i] Settings saved to ~/.ccs/config.yaml
|
||||
[i] Username: admin
|
||||
[i] Session timeout: 24 hours
|
||||
|
||||
Start dashboard: ccs config
|
||||
Show status: ccs config auth show
|
||||
Disable auth: ccs config auth disable
|
||||
```
|
||||
|
||||
### `ccs config auth show`
|
||||
|
||||
Display current authentication status.
|
||||
|
||||
```bash
|
||||
$ ccs config auth show
|
||||
|
||||
╭─────────────────────────────────╮
|
||||
│ Dashboard Auth Status │
|
||||
╰─────────────────────────────────╯
|
||||
|
||||
Configuration
|
||||
[OK] Authentication: Enabled
|
||||
[OK] Username: admin
|
||||
[i] Session timeout: 24 hours
|
||||
|
||||
Commands
|
||||
ccs config auth setup Configure authentication
|
||||
ccs config auth disable Disable authentication
|
||||
ccs config Open dashboard
|
||||
```
|
||||
|
||||
### `ccs config auth disable`
|
||||
|
||||
Disable dashboard authentication with confirmation.
|
||||
|
||||
```bash
|
||||
$ ccs config auth disable
|
||||
|
||||
╭─────────────────────────────────╮
|
||||
│ Disable Dashboard Auth │
|
||||
╰─────────────────────────────────╯
|
||||
|
||||
[!] This will disable login protection for the dashboard.
|
||||
[i] Anyone with network access will be able to view the dashboard.
|
||||
|
||||
Disable authentication? [y/N]: y
|
||||
|
||||
[OK] Dashboard authentication disabled
|
||||
|
||||
[i] Credentials preserved - re-enable with: ccs config auth setup
|
||||
```
|
||||
|
||||
### `ccs config auth --help`
|
||||
|
||||
Display usage information.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Environment variables override `config.yaml` values:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `CCS_DASHBOARD_AUTH_ENABLED` | Enable/disable auth (`true`/`false`) |
|
||||
| `CCS_DASHBOARD_USERNAME` | Username |
|
||||
| `CCS_DASHBOARD_PASSWORD_HASH` | Bcrypt password hash |
|
||||
|
||||
### Generating a Password Hash
|
||||
|
||||
Use bcrypt to generate a hash:
|
||||
|
||||
```bash
|
||||
# Using Node.js
|
||||
node -e "console.log(require('bcrypt').hashSync('your-password', 10))"
|
||||
|
||||
# Using npx
|
||||
npx bcrypt-cli hash "your-password"
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are stored in `~/.ccs/config.yaml`:
|
||||
|
||||
```yaml
|
||||
# Dashboard Auth: Optional login protection for CCS dashboard
|
||||
# Generate password hash: npx bcrypt-cli hash "your-password"
|
||||
# ENV override: CCS_DASHBOARD_AUTH_ENABLED, CCS_DASHBOARD_USERNAME, CCS_DASHBOARD_PASSWORD_HASH
|
||||
dashboard_auth:
|
||||
enabled: true
|
||||
username: "admin"
|
||||
password_hash: "$2b$10$..."
|
||||
session_timeout_hours: 24
|
||||
```
|
||||
|
||||
## Security Notes
|
||||
|
||||
1. **Bcrypt hashing**: Passwords are hashed with bcrypt (10 rounds) before storage
|
||||
2. **Session cookies**: Sessions use HTTP-only cookies (not accessible via JavaScript)
|
||||
3. **Rate limiting**: Login attempts are rate-limited (5 per 15 minutes)
|
||||
4. **Fail-closed remote writes**: When auth is disabled, sensitive management routes allow localhost only
|
||||
5. **File permissions**: Config file is created with 0o600 permissions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Authentication not configured"
|
||||
|
||||
Run `ccs config auth setup` to configure credentials.
|
||||
|
||||
If you are using the integrated Docker stack, run that command inside `ccs-cliproxy`. Running it on the outer host shell updates a different config directory and will not unlock the running dashboard.
|
||||
|
||||
### Forgot password
|
||||
|
||||
Run `ccs config auth setup` again to set a new password.
|
||||
|
||||
### ENV override not working
|
||||
|
||||
Ensure the variable is exported:
|
||||
|
||||
```bash
|
||||
export CCS_DASHBOARD_AUTH_ENABLED=true
|
||||
export CCS_DASHBOARD_USERNAME=admin
|
||||
export CCS_DASHBOARD_PASSWORD_HASH='$2b$10$...'
|
||||
```
|
||||
|
||||
### Session expired immediately
|
||||
|
||||
Check `session_timeout_hours` in config. Default is 24 hours.
|
||||
|
||||
### "Invalid ... context_group ..."
|
||||
|
||||
This error comes from `PUT /api/config` when an account explicitly sets shared mode with an invalid group. Use a canonical group value (for example: `team-alpha`).
|
||||
|
||||
## See Also
|
||||
|
||||
- [Dashboard Auth Feature](https://ccs.kaitran.ca/features/dashboard-auth) - Full documentation
|
||||
- [Config Schema](https://ccs.kaitran.ca/reference/config-schema) - All config options
|
||||
@@ -1,112 +1,51 @@
|
||||
# Hardening Debt Burndown Tracker
|
||||
# Hardening Debt Burndown
|
||||
|
||||
Last Updated: 2026-06-18
|
||||
Owner: Stream D (`#542`); maintainability epic owner TBD (open Q5)
|
||||
This document defines the live measurement method. Completed phase narratives
|
||||
remain available in Git history and must not be copied forward as current state.
|
||||
|
||||
## Scope
|
||||
|
||||
Maintainability hardening groundwork with low-risk changes:
|
||||
The generated inventory measures:
|
||||
|
||||
- Inventory legacy shims/compatibility markers
|
||||
- Inventory sync filesystem usage, especially runtime hotpaths
|
||||
- Incrementally migrate hotpath sync I/O to async I/O with tests
|
||||
- synchronous filesystem calls across source and in runtime hotpaths;
|
||||
- legacy, shim, and compatibility markers;
|
||||
- typed-error adoption;
|
||||
- direct `console.error` and `console.warn` use outside CLI-UX exemptions;
|
||||
- structured logger coverage;
|
||||
- source files above maintainability size thresholds.
|
||||
|
||||
## How to Measure
|
||||
Runtime-hotpath metrics exclude test, fixture, and mock files. Repository-wide
|
||||
totals may include them when the metric explicitly says `all`.
|
||||
|
||||
Run:
|
||||
## Generate And Verify
|
||||
|
||||
```bash
|
||||
bun run report:hardening
|
||||
node scripts/hardening-inventory.js --check
|
||||
```
|
||||
|
||||
Generated artifacts:
|
||||
|
||||
- `docs/reports/hardening-inventory.json`
|
||||
- `docs/reports/hardening-inventory.md`
|
||||
- `docs/reports/hardening-inventory.json` for tooling;
|
||||
- `docs/reports/hardening-inventory.md` for review.
|
||||
|
||||
## Kickoff Baseline (Issue #542 Stream D)
|
||||
`--check` rebuilds the expected content in memory and compares both tracked
|
||||
artifacts byte-for-byte. Any source change that affects a metric requires
|
||||
regeneration in the same commit.
|
||||
|
||||
The current baseline is sourced from `docs/reports/hardening-inventory.json` after running `bun run report:hardening`.
|
||||
Baseline captured: `2026-02-12`.
|
||||
## Interpretation
|
||||
|
||||
| Metric | Baseline |
|
||||
|---|---:|
|
||||
| Sync fs occurrences (all) | 835 |
|
||||
| Sync fs files affected (all) | 100 |
|
||||
| Sync fs occurrences (runtime hotpaths) | 724 |
|
||||
| Sync fs files affected (runtime hotpaths) | 89 |
|
||||
| Legacy shim markers | 131 |
|
||||
| Legacy shim files affected | 56 |
|
||||
- Treat rankings as investigation queues, not automatic refactor mandates.
|
||||
- Characterize behavior before splitting large or compatibility-sensitive code.
|
||||
- Separate user-facing terminal output from operational diagnostics before
|
||||
migrating console calls.
|
||||
- Preserve intentional shims until their compatibility window and callers are
|
||||
verified.
|
||||
- Prefer trend comparisons from Git history when historical context is needed;
|
||||
do not turn this live method into a dated progress ledger.
|
||||
|
||||
## Initial Async I/O Migration Log
|
||||
## Review Contract
|
||||
|
||||
| Date | Area | Change | Safety Notes |
|
||||
|---|---|---|---|
|
||||
| 2026-02-12 | `src/web-server/jsonl-parser.ts` | Migrated `parseProjectDirectory()` directory listing from sync `readdirSync` to async `fs.promises.readdir` | Existing behavior kept (same filtering/fallback); covered by `tests/unit/jsonl-parser.test.ts` |
|
||||
|
||||
## Maintainability & Traceability Baseline (2026-06-18)
|
||||
|
||||
Baseline for the maintainability/traceability epic (`plans/260618-1346-maintainability-traceability-epic`). Sourced from `docs/reports/hardening-inventory.json` -> `maintainability` block after `bun run report:hardening`. Baseline captured: `2026-06-18`.
|
||||
|
||||
| Metric | Baseline | Epic target | Owner phase |
|
||||
|---|---:|---:|---|
|
||||
| typed-error adoption (typed / total throws) | 0.9% (4 / 431) | >40% in locked subdomains | P4 |
|
||||
| typed-error adoption (locked: cliproxy/quota, cliproxy/auth, web-server/routes, auth) | 0.0% (0 / 23) | >40% | P4 |
|
||||
| hotpath `console.error`/`warn` occurrences (non-exempt) | 931 (1091 total, 160 CLI-UX exempt) | < 10 | P3 |
|
||||
| hotpath `console.error`/`warn` files (non-exempt) | 134 | minimal | P3 |
|
||||
| files with `createLogger` | 35 / 685 (5.1%) | rise across all subdomains | P2/P3 |
|
||||
| subdomains with zero `createLogger` | 20 (incl. api, channels, config, delegation, dispatcher, docker, shared) | 0 in the named set | P2 |
|
||||
| files > 400 LOC | 95 | < 60 after P5+P6 | P5/P6 |
|
||||
| files > 600 LOC | 45 | drop | P5/P6 |
|
||||
| ESLint `no-new-throw-error` gate | not enforced | error + allowlist | P7 |
|
||||
| ESLint `max-lines` gate | not enforced | warn at 400 | P7 |
|
||||
| hardening report freshness | stale (2026-02-12) | < 30d gate in `validate:ci-parity` | P1 |
|
||||
|
||||
### Method
|
||||
|
||||
Metrics are grep-based and approximate (not a contract). Comments and string/template/regex literals are stripped before matching (`scripts/hardening-inventory.js#stripComments`). Subdomain granularity is 2-level under `src/cliproxy/` (`cliproxy/quota`, `cliproxy/auth`, ...) and 1-level elsewhere (`auth`, `config`, ...). The hotpath `console.error` count excludes CLI-UX print surfaces (`src/commands/`, `src/management/`, `src/utils/ui/`) which are legitimate user-facing terminal output. The typed-error denominator for the P4 target is LOCKED to the four named subdomains so the >40% goal cannot be gamed by narrowing scope. Re-baseline whenever the schema or method changes.
|
||||
|
||||
### Largest hotpath console.error offenders (2026-06-18)
|
||||
|
||||
| File | `console.error`/`warn` |
|
||||
|---|---:|
|
||||
| `src/utils/error-manager.ts` | 142 |
|
||||
| `src/cliproxy/accounts/account-safety.ts` | 56 |
|
||||
| `src/cliproxy/config/model-config.ts` | 32 |
|
||||
| `src/cliproxy/executor/arg-parser.ts` | 26 |
|
||||
| `src/dispatcher/flows/settings-flow.ts` | 26 |
|
||||
|
||||
## Progress Log
|
||||
|
||||
| Date | Phase | Change | Metric movement |
|
||||
|---|---|---|---|
|
||||
| 2026-06-18 | P2 | Express `withRequestContext` wrap; `CCS_REQUEST_ID` daemon forwarding + child re-anchor; logger toe-holds in delegation/docker. | zero-createLogger subdomains 20 -> 18 |
|
||||
| 2026-06-18 | P3 | Redaction gate (token-shape scrubbing in context + `Error.message` + message string). `tool-sanitization-proxy` private log subsystem deleted (13 sites -> existing `createLogger`). ~120 diagnostic `console.error` -> structured `createLogger` across proxy, web-server/routes, glmt, quota-fetchers, executors, delegation. User-facing `console.error` (CLI flows, arg-parser usage, installers, prompts, adapter launch errors, error display) migrated to `process.stderr.write` (preserves stderr output). `error-manager.ts` reclassified CLI-UX-exempt (user-facing display). | hotpath `console.error` 928 -> 267 (71%); createLogger files 35 -> 64 |
|
||||
| 2026-06-18 | P4 | `throw new Error` -> typed subclasses (ProfileError/AuthError/ConfigError/ProviderError/ValidationError) in cliproxy/auth, web-server/routes, auth. Error taxonomy made erasable (enum -> const, param-props -> fields) so the UI build accepts it. | typed adoption (locked subdomains) 0/23 -> 21/23 (91.3%); overall 0.9% -> 8.6% |
|
||||
| 2026-06-18 | P5 | Split 4 test-backed god-files into submodule dirs + barrels (shared-manager, cliproxy-stats-routes, persist-command, quota-subcommand). Public API preserved. | files > 400 LOC 95 -> 91 |
|
||||
| 2026-06-18 | P6 | Split 2 of 6 characterization-first god-files (quota-fetcher, quota-fetcher-gemini-cli; both had strong per-provider test coverage). 4 deferred. | files > 400 LOC 91 -> 89 |
|
||||
| 2026-06-18 | P7 | ESLint gates: `ccs/no-new-throw-error` (error, baseline-allowlisted) + `max-lines` (warn, 400). code-standards.md + logging-contract.md (error.code table). | gates enforced (0 lint errors on baseline) |
|
||||
|
||||
## Epic outcome (2026-06-18)
|
||||
|
||||
| Metric | Baseline | Final | Target | Status |
|
||||
|---|---:|---:|---:|---|
|
||||
| typed-error adoption (locked subdomains) | 0.0% (0/23) | 91.3% (21/23) | >40% | met |
|
||||
| typed-error adoption (overall) | 0.9% (5/431) | 8.6% (37/431) | rise | met |
|
||||
| hotpath `console.error`/`warn` | 928 | 267 | <10 | partial; high-risk diagnostics migrated + redaction gate closed, but metric target unmet (see P3 note) |
|
||||
| files with `createLogger` | 35 | 64 | rise | met |
|
||||
| subdomains with zero `createLogger` | 20 | 15 | 0 in named set | partial; logger toe-holds improved coverage but P2 zero-subdomain target unmet |
|
||||
| files > 400 LOC | 95 | 89 | <60 | partial (P6 deferred 4 targets) |
|
||||
| hardening report freshness | stale | <30d gate | gate | met (P1) |
|
||||
| ESLint `no-new-throw-error` | not enforced | error + allowlist | enforced | met (P7) |
|
||||
| ESLint `max-lines` | not enforced | warn at 400 | enforced | met (P7) |
|
||||
|
||||
### Deferred follow-ups
|
||||
|
||||
- P2 remaining zero-logger subdomains (`api`, `channels`, `config`, `dispatcher`, `shared`, and others): add lightweight logger toe-holds when those subdomains next receive behavior work. This epic improved coverage but did not satisfy the original `0` target.
|
||||
- P3 remaining 267 non-exempt `console.error`/`warn` call sites: split into true user-facing terminal output vs diagnostics, then either migrate diagnostics to structured logs or update the metric method to exempt confirmed CLI display helpers.
|
||||
- P6 remaining 4 targets (`oauth-handler.ts`, `cursor-executor.ts`, `tool-sanitization-proxy.ts`, +1): need dedicated characterization-test work before splitting (the plan's characterization-first hard gate). Each has a clean seam identified in the epic plan.
|
||||
|
||||
### P3 residual note (2026-06-18)
|
||||
|
||||
The remaining 267 non-exempt `console.error`/`warn` occurrences are not fully resolved by this epic. Many are user-facing terminal display paths (interactive flows, arg-parser usage errors, installers, prompts, adapter launch failures, error-display helpers), but the generated metric still counts them because its exemption list is intentionally conservative. Treat P3 as partial until the residual list is classified file-by-file and either migrated to structured logs or explicitly moved into the CLI-UX exemption method. The redaction gate now covers structured message, context, `Error.message`, and `StageOptions.error` metadata so further diagnostic migration is safe.
|
||||
A hardening change is complete only when focused tests pass, both inventory
|
||||
artifacts match the current source tree, and the diff explains any material
|
||||
metric movement.
|
||||
+6
-117
@@ -1,119 +1,8 @@
|
||||
# Image Analysis Configuration Guide
|
||||
# Image Analysis
|
||||
|
||||
CCS provides first-class image and PDF analysis for third-party Claude launches that do not have reliable native vision support.
|
||||
User setup, routing behavior, and troubleshooting live in the canonical
|
||||
[Image Analysis guide](https://docs.ccs.kaitran.ca/features/ai/image-analysis).
|
||||
|
||||
## How Image Analysis Works
|
||||
|
||||
Native Claude accounts keep Anthropic's own vision flow.
|
||||
|
||||
Third-party profiles now use a CCS-managed local MCP tool named `ImageAnalysis` when the runtime is available. CCS also appends a short steering hint so Claude prefers that tool over `Read` for local image and PDF files.
|
||||
|
||||
Healthy Claude-target launches suppress the legacy CCS `Read` hook so MCP stays authoritative. If the managed runtime cannot be provisioned, CCS keeps the old `Read` hook available only as a compatibility fallback when that path is still viable. If runtime/auth/proxy readiness is degraded beyond that, CCS falls back to native `Read` instead of failing the whole launch.
|
||||
|
||||
## Routing Model
|
||||
|
||||
ImageAnalysis requests go straight to the CCS-managed provider route:
|
||||
|
||||
```text
|
||||
Claude -> ccs-image-analysis MCP -> CCS provider route -> /api/provider/<backend>/v1/messages
|
||||
```
|
||||
|
||||
Important:
|
||||
- CCS does not relay image analysis through Claude Code, another CLI, or a second model wrapper.
|
||||
- For bridge-backed settings profiles, CCS resolves the backend and provider path before launch.
|
||||
- CCS avoids leaking a profile's ordinary third-party `ANTHROPIC_BASE_URL` or token into image analysis unless that profile is explicitly using a CLIProxy bridge.
|
||||
|
||||
## Profile Behavior
|
||||
|
||||
| Profile Type | Image Method |
|
||||
|--------------|--------------|
|
||||
| Claude `default` / `account` | Native Claude vision / native `Read` |
|
||||
| Third-party settings / CLIProxy / Copilot | CCS local `ImageAnalysis` MCP tool when ready |
|
||||
| Third-party when MCP provisioning fails but provider-backed analysis is still viable | Legacy CCS `Read` hook fallback |
|
||||
| Third-party when runtime/auth/proxy is unavailable | Native `Read` fallback |
|
||||
|
||||
## Configuration
|
||||
|
||||
Configure via dashboard (`Settings -> Image`) or `~/.ccs/config.yaml`:
|
||||
|
||||
```yaml
|
||||
image_analysis:
|
||||
enabled: true
|
||||
timeout: 60
|
||||
fallback_backend: agy
|
||||
provider_models:
|
||||
agy: gemini-3-1-flash-preview
|
||||
codex: gpt-5.1-codex-mini
|
||||
ghcp: claude-haiku-4.5
|
||||
```
|
||||
|
||||
Useful commands:
|
||||
|
||||
```bash
|
||||
ccs config image-analysis
|
||||
ccs config image-analysis --enable
|
||||
ccs config image-analysis --disable
|
||||
ccs config image-analysis --set-fallback agy
|
||||
ccs config image-analysis --set-profile-backend glm agy
|
||||
ccs config image-analysis --clear-profile-backend glm
|
||||
```
|
||||
|
||||
## Prompt Templates
|
||||
|
||||
CCS installs editable prompt templates at:
|
||||
|
||||
```text
|
||||
~/.ccs/prompts/image-analysis/
|
||||
```
|
||||
|
||||
Templates:
|
||||
- `default.txt`
|
||||
- `screenshot.txt`
|
||||
- `document.txt`
|
||||
|
||||
CCS automatically selects `screenshot` for screenshot-like filenames, `document` for PDFs, and `default` otherwise.
|
||||
|
||||
## Runtime Environment
|
||||
|
||||
Key runtime env vars:
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `CCS_IMAGE_ANALYSIS_SKIP` | Disable image analysis for the current launch |
|
||||
| `CCS_IMAGE_ANALYSIS_SKIP_HOOK` | Suppress only the legacy CCS `Read` hook while keeping MCP ImageAnalysis available |
|
||||
| `CCS_IMAGE_ANALYSIS_RUNTIME_BASE_URL` | Explicit CCS runtime base URL |
|
||||
| `CCS_IMAGE_ANALYSIS_RUNTIME_PATH` | Provider route such as `/api/provider/agy` |
|
||||
| `CCS_IMAGE_ANALYSIS_RUNTIME_API_KEY` | Explicit CCS runtime auth key |
|
||||
| `CCS_IMAGE_ANALYSIS_MODEL` | Force a single image-analysis model |
|
||||
| `CCS_DEBUG` | Verbose runtime logging |
|
||||
|
||||
## Self-Heal
|
||||
|
||||
CCS now auto-heals stale managed image-analysis state in three places:
|
||||
|
||||
- Healthy Claude launches remove stale CCS-managed image `Read` hooks from the active profile settings before launch.
|
||||
- `Settings -> Image` save/provisioning repairs managed MCP runtime files, syncs managed MCP entries into isolated Claude config dirs, and cleans stale CCS-managed image hooks from `~/.ccs/*.settings.json`.
|
||||
- `ccs doctor --fix` repairs invalid image-analysis config, removes stale CCS-managed image hooks, and resyncs managed `ccs-image-analysis` MCP entries into isolated configs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Claude still uses `Read`
|
||||
|
||||
- Confirm `ccs config image-analysis` shows `enabled: true`
|
||||
- Check the active profile resolves to a configured backend
|
||||
- Run `ccs doctor --fix` to repair stale managed hooks or missing managed MCP sync
|
||||
- Run with `CCS_DEBUG=1` to see runtime preparation details
|
||||
|
||||
### ImageAnalysis is not exposed
|
||||
|
||||
- Verify CLIProxy auth for the resolved backend
|
||||
- Verify the local or remote CLIProxy target is reachable
|
||||
- Check `~/.claude.json` and inherited account configs for `ccs-image-analysis`
|
||||
|
||||
### I need to prove requests are going directly to the provider route
|
||||
|
||||
Run with `CCS_DEBUG=1` and inspect the resolved runtime path. The request target should be provider-scoped, for example:
|
||||
|
||||
```text
|
||||
/api/provider/agy/v1/messages
|
||||
```
|
||||
Contributor implementation contracts are enforced by the source and focused
|
||||
tests under `src/utils/image-analysis/`, `src/utils/hooks/`, and
|
||||
`src/web-server/routes/`.
|
||||
@@ -1,378 +1,44 @@
|
||||
# OpenAI-Compatible Provider Routing
|
||||
# OpenAI-Compatible Proxy Developer Contract
|
||||
|
||||
CCS can route Claude Code traffic through a local Anthropic-compatible proxy when
|
||||
your API profile points at an OpenAI-compatible chat completions endpoint.
|
||||
The canonical
|
||||
[OpenAI-Compatible Provider Routing guide](https://docs.ccs.kaitran.ca/features/proxy/openai-compatible-providers)
|
||||
owns user setup and workflows. This local contract retains runtime and
|
||||
configuration invariants used by source, tests, and operators.
|
||||
|
||||
This is useful for providers such as:
|
||||
## Profile-Scoped Insecure TLS
|
||||
|
||||
- Hugging Face Inference Providers
|
||||
- Tuning Engines
|
||||
- OpenRouter
|
||||
- Ollama
|
||||
- llama.cpp servers
|
||||
- OpenAI-compatible self-hosted gateways
|
||||
`CCS_OPENAI_PROXY_INSECURE` is read from an OpenAI-compatible profile env.
|
||||
Truthy values are `1`, `true`, `yes`, and `on` (case-insensitive). When enabled,
|
||||
the local proxy disables upstream certificate verification for that profile,
|
||||
including request-time routing to another insecure profile.
|
||||
|
||||
## Related Project: claude-code-router
|
||||
This flag weakens TLS verification. Keep it explicit and profile-scoped; never
|
||||
make it a global default or infer it from a failed certificate check. The live
|
||||
resolution and dispatcher contracts are in
|
||||
[`profile-router.ts`](../src/proxy/profile-router.ts) and
|
||||
[`proxy-server.ts`](../src/proxy/server/proxy-server.ts).
|
||||
|
||||
[claude-code-router](https://github.com/musistudio/claude-code-router) is the
|
||||
main external reference that informed this CCS work. Their Anthropic/OpenAI
|
||||
transformer design helped shape the routing approach here.
|
||||
## Request Timeout
|
||||
|
||||
When to use CCR:
|
||||
`CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS` controls the upstream request timeout in
|
||||
milliseconds:
|
||||
|
||||
- you want a standalone router without CCS profile integration
|
||||
- you do not need CCS account/runtime management around the request flow
|
||||
- default: `600000` (10 minutes);
|
||||
- accepted: values whose `Number.parseInt` result is positive;
|
||||
- missing, invalid, zero, or negative values: fall back to the default.
|
||||
|
||||
When to use CCS:
|
||||
Upstream Undici header/body timeouts must stay above the request timeout so they
|
||||
do not terminate slow self-hosted inference first. The current implementation
|
||||
adds a 30-second grace ceiling. Source of truth:
|
||||
[`messages-route.ts`](../src/proxy/server/messages-route.ts).
|
||||
|
||||
- you already use CCS API profiles or runtime bridges
|
||||
- you want the proxy flow available through `ccs <profile>` and `ccs proxy ...`
|
||||
- you want the routing behavior documented and tested inside the CCS workflow
|
||||
## Compatibility Boundary
|
||||
|
||||
## What CCS Does
|
||||
These variables configure the local Anthropic-to-OpenAI proxy. They do not
|
||||
convert a non-compatible profile into a compatible one, bypass local proxy
|
||||
authentication, or authorize remote binding. Keep profile detection, adaptive
|
||||
port selection, passthrough mode, request-time routing, and scenario routing
|
||||
documented in the public guide.
|
||||
|
||||
When you launch a compatible settings profile with the Claude target, CCS now:
|
||||
|
||||
1. Starts a local proxy on `127.0.0.1` using the resolved local port for that profile
|
||||
2. Accepts Anthropic `/v1/messages` traffic from Claude Code
|
||||
3. Translates requests into OpenAI chat-completions format
|
||||
4. Forwards them to your configured upstream provider
|
||||
5. Translates streaming responses back into Anthropic SSE
|
||||
|
||||
You do not need to rewrite your profile by hand each time.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Create or reuse an API profile that points at an OpenAI-compatible endpoint:
|
||||
|
||||
```bash
|
||||
ccs api create --preset hf
|
||||
```
|
||||
|
||||
For Tuning Engines:
|
||||
|
||||
```bash
|
||||
ccs api create --preset te
|
||||
```
|
||||
|
||||
Then you can use the profile directly:
|
||||
|
||||
```bash
|
||||
ccs hf
|
||||
# or
|
||||
ccs te
|
||||
```
|
||||
|
||||
CCS detects that the profile is OpenAI-compatible and auto-routes Claude Code
|
||||
through the local proxy.
|
||||
|
||||
## Manual Proxy Lifecycle
|
||||
|
||||
If you want to manage the proxy explicitly:
|
||||
|
||||
```bash
|
||||
ccs proxy start hf
|
||||
eval "$(ccs proxy activate)"
|
||||
ccs proxy status
|
||||
ccs proxy stop
|
||||
```
|
||||
|
||||
Useful variants:
|
||||
|
||||
```bash
|
||||
ccs proxy start hf --host 127.0.0.1
|
||||
ccs proxy start hf --port 3460
|
||||
ccs proxy activate hf
|
||||
ccs proxy activate --fish
|
||||
ccs proxy status hf
|
||||
ccs proxy stop hf
|
||||
```
|
||||
|
||||
Port selection precedence is:
|
||||
|
||||
1. CLI `--port` for an exact one-off pin
|
||||
2. `proxy.profile_ports[profile]` for an exact per-profile pin
|
||||
3. `proxy.port` for a shared preferred starting port
|
||||
4. adaptive per-profile fallback when nothing is pinned
|
||||
|
||||
Legacy shared `proxy.port: 3456` values are treated as unset so older configs
|
||||
move onto the adaptive path instead of staying on the hot legacy default. If
|
||||
you need an exact `3456` binding now, pin it via `--port` or `proxy.profile_ports`.
|
||||
|
||||
`ccs proxy activate` now prints the full local runtime contract:
|
||||
|
||||
- `ANTHROPIC_BASE_URL`
|
||||
- `ANTHROPIC_AUTH_TOKEN`
|
||||
- `ANTHROPIC_MODEL` plus tier defaults when present
|
||||
- `DISABLE_TELEMETRY`
|
||||
- `DISABLE_COST_WARNINGS`
|
||||
- `API_TIMEOUT_MS`
|
||||
- `NO_PROXY`
|
||||
|
||||
## Multiple Active Proxy Profiles
|
||||
|
||||
CCS now stores OpenAI-compatible proxy state per profile instead of treating the
|
||||
runtime as a singleton.
|
||||
|
||||
- Different compatible profiles can run at the same time on separate local ports
|
||||
- `ccs proxy activate` without a profile stays convenient when only one proxy is
|
||||
running
|
||||
- When multiple proxies are running, pass the profile explicitly to
|
||||
`activate`, `status`, or `stop`
|
||||
- `status` and `activate` always reflect the actual running port instead of an
|
||||
assumed default
|
||||
|
||||
If you want to pin or guide ports explicitly, configure them in `~/.ccs/config.yaml`:
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
port: 45000
|
||||
profile_ports:
|
||||
hf: 3460
|
||||
openai: 3461
|
||||
```
|
||||
|
||||
## Request-Time Routing
|
||||
|
||||
The proxy is no longer limited to the startup profile's default model.
|
||||
|
||||
Supported request-time selectors:
|
||||
|
||||
- `profile:model`
|
||||
Example: `deepseek:deepseek-reasoner`
|
||||
- `profile`
|
||||
Example: `openrouter`
|
||||
- plain model ids
|
||||
Example: `deepseek-chat`
|
||||
|
||||
Plain model ids use exact string equality against the configured profile model
|
||||
slots (`model`, `opusModel`, `sonnetModel`, `haikuModel`). CCS does not apply
|
||||
fuzzy matching or prefix matching here. If no exact match is found, the request
|
||||
stays on the active profile with the requested model id unchanged.
|
||||
|
||||
Routing behavior:
|
||||
|
||||
1. `profile:model` wins immediately.
|
||||
2. Scenario routing may override the active profile when configured.
|
||||
3. Plain model ids are matched against the configured OpenAI-compatible
|
||||
profiles before falling back to the active profile.
|
||||
|
||||
This means a Claude session launched through one compatible profile can still
|
||||
request another compatible profile/model when the proxy can resolve it safely.
|
||||
|
||||
## Scenario Routing
|
||||
|
||||
Scenario routing is now supported through `proxy.routing` in your CCS config.
|
||||
|
||||
Example `~/.ccs/config.yaml`:
|
||||
|
||||
```yaml
|
||||
proxy:
|
||||
routing:
|
||||
default: "deepseek:deepseek-chat"
|
||||
background: "ollama:qwen2.5-coder:0.5b"
|
||||
think: "deepseek:deepseek-reasoner"
|
||||
longContext: "openrouter:google/gemini-2.5-pro"
|
||||
longContextThreshold: 60000
|
||||
webSearch: "openrouter:perplexity/sonar-pro"
|
||||
```
|
||||
|
||||
Current scenario detection:
|
||||
|
||||
- `background`: requested model contains `haiku`
|
||||
- `think`: Anthropic `thinking` is enabled
|
||||
- `longContext`: estimated request tokens exceed `longContextThreshold`
|
||||
- `webSearch`: tool list includes `web_search`
|
||||
- `default`: fallback selector when the above do not apply
|
||||
|
||||
Routing decisions are logged through CCS structured logs.
|
||||
|
||||
`longContextThreshold` uses an intentionally approximate token estimate based on
|
||||
message characters, tool payload size, and a `chars / 4` heuristic. Tune the
|
||||
threshold conservatively if your routing decision needs a sharper cutoff near
|
||||
the boundary.
|
||||
|
||||
## How Profile Detection Works
|
||||
|
||||
CCS keeps these profiles in the normal API/settings-profile flow.
|
||||
|
||||
Anthropic-compatible endpoints such as:
|
||||
|
||||
- `https://api.anthropic.com`
|
||||
- `https://api.z.ai/api/anthropic`
|
||||
- `https://api.deepseek.com/anthropic`
|
||||
|
||||
continue to launch directly.
|
||||
|
||||
OpenAI-compatible endpoints such as:
|
||||
|
||||
- `https://router.huggingface.co/v1`
|
||||
- `https://api.openai.com/v1`
|
||||
- `http://localhost:11434`
|
||||
|
||||
are routed through the local proxy for Claude-target launches.
|
||||
|
||||
## Provider Setup
|
||||
|
||||
### DeepSeek
|
||||
|
||||
Use a settings profile whose env looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/v1",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-...",
|
||||
"ANTHROPIC_MODEL": "deepseek-chat",
|
||||
"CCS_DROID_PROVIDER": "generic-chat-completion-api"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Typical override target:
|
||||
|
||||
- `deepseek:deepseek-reasoner`
|
||||
|
||||
### OpenRouter
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://openrouter.ai/api/v1",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-or-...",
|
||||
"ANTHROPIC_MODEL": "openai/gpt-4.1-mini",
|
||||
"CCS_DROID_PROVIDER": "generic-chat-completion-api"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Useful when you want:
|
||||
|
||||
- model fan-out behind one provider profile
|
||||
- long-context or web-search scenario targets
|
||||
|
||||
### Ollama / Local Gateways
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "http://127.0.0.1:11434",
|
||||
"ANTHROPIC_AUTH_TOKEN": "ollama",
|
||||
"ANTHROPIC_MODEL": "qwen3-coder",
|
||||
"CCS_DROID_PROVIDER": "generic-chat-completion-api"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For self-signed HTTPS gateways, add `CCS_OPENAI_PROXY_INSECURE=1`.
|
||||
|
||||
### DashScope / Qwen Compatible Mode
|
||||
|
||||
DashScope's compatible endpoint works even when older settings files still
|
||||
carry a stale Anthropic-style provider hint:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"ANTHROPIC_BASE_URL": "https://dashscope-us.aliyuncs.com/compatible-mode/v1",
|
||||
"ANTHROPIC_AUTH_TOKEN": "sk-...",
|
||||
"ANTHROPIC_MODEL": "qwen3.6-plus",
|
||||
"CCS_DROID_PROVIDER": "anthropic"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
CCS now infers the OpenAI-compatible route from the base URL and does not let
|
||||
that stale provider hint block proxy routing.
|
||||
|
||||
## Self-Signed TLS
|
||||
|
||||
If your upstream gateway uses a self-signed or privately issued certificate,
|
||||
set this in the profile settings JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"CCS_OPENAI_PROXY_INSECURE": "1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
That flag is respected by both:
|
||||
|
||||
- `ccs <profile>` auto-routing
|
||||
- `ccs proxy start <profile>`
|
||||
|
||||
## Supported Runtime Paths
|
||||
|
||||
- `ccs <profile>` with Claude target: auto-starts the local proxy when needed
|
||||
- `ccs proxy start <profile>`: starts the proxy explicitly
|
||||
- `GET /`: proxy info and bound profile details
|
||||
- `GET /health`: proxy liveness check
|
||||
- `GET /v1/models`: local view of the configured model mapping
|
||||
- `POST /v1/messages`: Anthropic-compatible request entrypoint
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Missing or invalid local proxy token
|
||||
|
||||
- Re-run `eval "$(ccs proxy activate)"`
|
||||
- Check `ccs proxy status` and confirm the expected profile is running
|
||||
|
||||
### Self-signed or private CA upstream
|
||||
|
||||
- Add `CCS_OPENAI_PROXY_INSECURE=1` to the profile settings
|
||||
- Restart the proxy after changing the setting
|
||||
|
||||
### Need to pin or verify the local port
|
||||
|
||||
- Check the active binding with `ccs proxy status hf`
|
||||
- Pin a one-off port with `ccs proxy start hf --port 3460`
|
||||
- Reserve a stable profile port with `proxy.profile_ports`
|
||||
- Re-run `ccs proxy activate hf` after changing the port
|
||||
|
||||
### Provider returns `429` or empty upstream output
|
||||
|
||||
- CCS now preserves upstream rate-limit errors and retry headers
|
||||
- Empty or malformed provider JSON is returned as Anthropic-style `api_error`
|
||||
|
||||
### Slow upstreams: `socket connection was closed unexpectedly`
|
||||
|
||||
- Long-running upstreams (self-hosted LLMs with queue/prefill phases) can stay
|
||||
silent for minutes before the first or next token. The proxy already allows up
|
||||
to 10 minutes per request; set `CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS` in the
|
||||
profile settings to raise or lower that ceiling.
|
||||
- Restart the proxy after changing the setting.
|
||||
|
||||
### Requests route to the wrong model/profile
|
||||
|
||||
- Use an explicit selector such as `profile:model`
|
||||
- Review `proxy.routing` if scenario routing is enabled
|
||||
- Check CCS structured logs in `~/.ccs/logs/current.jsonl` for routing decisions
|
||||
|
||||
## Validation
|
||||
|
||||
The shipped coverage includes:
|
||||
|
||||
- unit tests for OpenAI-compatible profile detection
|
||||
- unit tests for Anthropic -> OpenAI request translation
|
||||
- unit tests for request-time profile/model routing and scenario routing
|
||||
- unit tests for multi-line SSE parsing
|
||||
- integration tests for `/v1/messages` request/response translation
|
||||
- integration tests for rate limits, empty upstream responses, timeout handling,
|
||||
thinking/tool-call chunk streaming, and request-time routing
|
||||
- integration tests for daemon lifecycle and `/health` / `/v1/models`
|
||||
- e2e tests for `ccs proxy` lifecycle
|
||||
- e2e tests for `ccs <profile>` auto-routing through a mock upstream
|
||||
|
||||
Focused verification command:
|
||||
|
||||
```bash
|
||||
bun test tests/e2e/proxy-command.e2e.test.ts tests/integration/proxy/request-routing.test.ts --coverage
|
||||
```
|
||||
|
||||
Pre-merge gate:
|
||||
|
||||
```bash
|
||||
bun run validate
|
||||
```
|
||||
Focused behavior locks live in `tests/unit/proxy/` and
|
||||
`tests/integration/proxy/`.
|
||||
+35
-268
@@ -1,284 +1,51 @@
|
||||
# CCS Project Roadmap
|
||||
|
||||
Last Updated: 2026-05-22
|
||||
This repository does not maintain a hand-copied backlog snapshot. Current work
|
||||
lives in GitHub so status, ownership, and dependencies stay live.
|
||||
|
||||
Forward-looking roadmap documenting current priorities, GitHub issues, and future feature plans.
|
||||
## Find Current Work
|
||||
|
||||
---
|
||||
- [Open issues](https://github.com/kaitranntt/ccs/issues)
|
||||
- [Project boards](https://github.com/users/kaitranntt/projects)
|
||||
- [Open pull requests](https://github.com/kaitranntt/ccs/pulls)
|
||||
- [Releases](https://github.com/kaitranntt/ccs/releases)
|
||||
|
||||
## Completed Modularization Summary
|
||||
Filter issues by area and type labels before selecting work. Verify the issue
|
||||
body, recent comments, and linked pull requests rather than relying on an old
|
||||
roadmap copy.
|
||||
|
||||
All major modularization work is complete. The codebase evolved from monolithic files to a well-structured modular architecture.
|
||||
## Planning Guidance
|
||||
|
||||
| Phase | Description | Key Result |
|
||||
|-------|-------------|------------|
|
||||
| 1 | Type System | `src/types/` with barrel exports |
|
||||
| 2 | CLI Commands | `src/commands/` (8 handlers extracted) |
|
||||
| 3 | CLIProxy | `src/cliproxy/` with auth/, binary/, services/ subdirs |
|
||||
| 4 | Utils/Errors | `src/utils/ui/`, `src/errors/`, `src/management/` |
|
||||
| 5 | UI Components | 5 monster files split into modular dirs (54+ modules) |
|
||||
| 6 | Settings Page | `pages/settings/` (1,781->20 files) |
|
||||
| 7 | Analytics Page | `pages/analytics/` (420->8 files) |
|
||||
| 8 | Auth Monitor | `monitoring/auth-monitor/` (465->8 files) |
|
||||
| 9 | Test Infrastructure | 1407 tests, 90% coverage |
|
||||
| 10 | Remote CLIProxy | `proxy-config-resolver.ts`, `remote-proxy-client.ts` |
|
||||
| 11 | Kiro + legacy ghcp Providers | OAuth support via CLIProxyAPIPlus (v7.2) |
|
||||
| 12 | Hybrid Quota Management | `quota-manager.ts`, `quota-fetcher.ts` (v7.14) |
|
||||
| 13 | Docker Support | `docker/` directory with Dockerfile, Compose, entrypoint |
|
||||
| 14 | Image Analysis Hook | Vision proxying via CLIProxy transformers (v7.34) |
|
||||
| 15 | Third-Party Tool Integration | `ccs env` command with multi-format export (v7.39) |
|
||||
1. Confirm the issue still reproduces against `origin/dev`.
|
||||
2. Read `CLAUDE.md` and the affected source, tests, help, and public docs.
|
||||
3. Keep the implementation and compatibility boundary explicit.
|
||||
4. Run focused validation, then the repository quality gate appropriate to the
|
||||
change.
|
||||
5. State public-doc impact before handoff.
|
||||
|
||||
**Metrics Achieved**:
|
||||
- Files >500 lines: 12 -> 5 (-58%)
|
||||
- UI files >200 lines: 28 -> 8 (-71%)
|
||||
- Barrel exports: 5 -> 39 (+680%)
|
||||
- Test coverage: 0% -> 90%
|
||||
- Total tests: 1440 (6 skipped)
|
||||
Plans are workspace-only and belong under ignored `plans/`; they are not product
|
||||
roadmap truth.
|
||||
|
||||
---
|
||||
## Maintainability Work
|
||||
|
||||
## Current Status
|
||||
Use [Hardening Debt Burndown](./hardening-debt-burndown.md) for measurement
|
||||
methodology. Its generated inventory is an exact source-tree snapshot, not a
|
||||
historical progress log.
|
||||
|
||||
### Recent Fixes
|
||||
## Stable Product Direction
|
||||
|
||||
- **2026-05-22**: **#1337** CLIProxy quota preflight now reconciles the entire
|
||||
active provider account pool before local proxy startup. Known-exhausted
|
||||
non-default accounts move out of live `auth/` rotation when another healthy
|
||||
account exists, preventing round-robin requests from stalling on already
|
||||
exhausted accounts while preserving manual pause/resume and cooldown
|
||||
auto-resume behavior.
|
||||
- **2026-05-22**: **#1332** `ccsx auth` profiles now link native Codex
|
||||
`agents/` and `skills/` resources alongside `config.toml`, and `ccsx <profile>`
|
||||
repairs missing resource links at launch so relative agent role `config_file`
|
||||
entries do not warn on existing profiles.
|
||||
- **2026-05-20**: **#1285** Codex model picker aliases now include `minimal` and `low` reasoning effort suffixes alongside `medium`, `high`, and `xhigh`, and the dashboard separates base recommendations, reasoning variants, and fast variants so low-effort selections such as `gpt-5.5-low` are visible.
|
||||
- **2026-05-19**: **#1252** Claude extension persistence now rejects Codex CLIProxy profiles instead of writing `ANTHROPIC_BASE_URL=/api/provider/codex` into Claude Code settings. Native Codex subscription use stays on `ccsxp` or `ccs codex --target codex`, and `ccs doctor` warns when stale Claude settings still point at the Codex translator.
|
||||
- **2026-05-18**: **#1287** `ccsx resume` now passes through to the native Codex `resume` subcommand before CCS profile detection, preserving Codex session continuation while keeping `ccsx auth` on the managed Codex profile namespace.
|
||||
- **2026-05-09**: **#1199** Existing Claude auth accounts now have dashboard-visible Shared Resources controls separate from History Sync. Accounts exposes a dedicated Resources action for `shared` vs `profile-local`, `/shared` now inventories commands, skills, agents, plugins, and `settings.json`, plugin directories without docs show factual directory contents, and shared settings content is inspectable read-only through the localhost-gated shared-content API.
|
||||
- **2026-05-07**: **#760** Codex GPT fast mode is now a first-class CLIProxy model tuning suffix. CCS accepts `gpt-5.4-fast`, `gpt-5.4-high-fast`, and equivalent canonicalized forms in raw env configs, CLI variant creation, and the dashboard model picker; runtime requests now send the base upstream model with `reasoning.effort` plus `service_tier: "priority"` instead of leaking the suffixed alias to CLIProxy upstream routing.
|
||||
- **2026-05-07**: **#1103** GitHub Copilot is now treated as a deprecated compatibility bridge. The dashboard moves Copilot out of the active Identity & Access section and into Deprecated, quick setup no longer offers `ghcp` for new onboarding, CLI/help/config copy marks Copilot as deprecated, and existing `ccs copilot` / `ghcp` compatibility paths remain available for current setups.
|
||||
- **2026-05-07**: **#1189** Headless settings-profile delegation now preserves native Claude passthrough args without a Claude flag allowlist. Explicit `--channels` values reach Claude Code, future native flags can carry multiple adjacent values, malformed CCS-owned flags no longer swallow the next native flag, and `--prompt=<text>` routes through headless delegation consistently with `--prompt <text>`.
|
||||
- **2026-05-03**: **#1172** Local CLIProxy config generation now keeps the CPAMC management dashboard aligned with backend selection. `backend: original` points to the upstream dashboard, `backend: plus` points to the CCS-maintained CPAMC fork, `cliproxy.management_panel_repository` lets advanced users override the panel repository, and stale generated configs are regenerated when the expected panel source changes.
|
||||
- **2026-04-30**: **#1153** Native Claude launches now accept session-scoped `--effort low|medium|high|xhigh|max` overrides through CCS without mutating global Claude settings. CCS validates invalid or missing effort values before spawning Claude, normalizes accepted values, keeps default headless `-p/--prompt` launches on native Claude instead of delegation parsing, and preserves CLIProxy/Codex/Droid effort aliases.
|
||||
- **2026-04-28**: **#1123** CLIProxy quota failover now uses the dashboard/manual pause mechanism for all quota-visible OAuth providers with CCS quota fetchers: Antigravity, Claude, Codex, Gemini CLI, and GitHub Copilot. When a healthy fallback exists, CCS moves the exhausted account token out of the live `auth/` folder into `auth-paused/`, marks the account paused for dashboard visibility, persists the cooldown for auto-resume, and still avoids self-pausing the last usable account.
|
||||
- **2026-04-28**: **#1115** CCS now exposes upstream CLIProxy session affinity as a first-class local managed setting. Users can inspect and toggle local `session-affinity` plus TTL from `ccs cliproxy routing affinity`, from the `/cliproxy` dashboard routing card, and through the local dashboard API. The generated local CLIProxy config now persists `routing.session-affinity` and `routing.session-affinity-ttl`, help/copy explains that CLIProxy prefers explicit session or thread identifiers before falling back to prompt-history hashing, and remote session-affinity management stays explicitly unsupported until upstream management APIs expose more than `routing.strategy`.
|
||||
- **2026-04-24**: **#1065** Local CLIProxy Plus is available again as an explicit opt-in backend through the community-maintained `kaitranntt/CLIProxyAPIPlus` fork. CCS keeps `original` as the default backend, no longer downgrades saved `backend: plus` configs to `original`, updates Plus release lookups to the maintained fork, and documents Plus as a targeted path for plus-only providers.
|
||||
- **2026-04-21**: CLIProxy quota failover now quarantines exhausted Claude and Antigravity accounts out of live rotation when a healthy fallback exists. CCS persists those quota-triggered pauses across launches, automatically resumes them after the configured cooldown window, and deliberately avoids auto-pausing the last available account so single-account setups still degrade gracefully instead of hard-locking themselves.
|
||||
- **2026-04-20**: **#1051** Browser automation now defaults safe-off for new installs and upgrades that do not already carry explicit browser settings. CCS changes both Claude Browser Attach and Codex Browser Tools to start with `enabled: false` and `policy: manual`, normalizes missing browser policies on upgrade back to `manual`, preserves explicit existing enablement, and updates status/help/docs so browser tooling is never implied to auto-expose unless users opt in.
|
||||
- **2026-04-19**: **#1051** Browser tooling now has an explicit exposure policy instead of only coarse enablement toggles. CCS adds `browser.<lane>.policy` (`auto` or `manual`) for both Claude Browser Attach and Codex Browser Tools, exposes CLI-first policy controls through `ccs browser policy`, `ccs browser enable`, and `ccs browser disable`, and adds one-run launch overrides `--browser` and `--no-browser` so users can force browser tooling on or off without editing saved config.
|
||||
- **2026-04-19**: **#1049** Browser setup now has a real remediation path instead of status/doctor-only guidance. CCS adds `ccs browser setup` as the primary one-command flow for Claude Browser Attach, shortens managed browser-path output to home-relative display paths where appropriate, and updates browser readiness guidance to point users at setup first while keeping browser doctor read-only by default.
|
||||
- **2026-04-18**: **#1038** Legacy OpenAI-compatible provider writes no longer self-destruct on the next `ccs cliproxy restart`. CCS now preserves AI-provider-managed top-level sections such as `openai-compatibility` during CLIProxy config regeneration, and the legacy `openai-compat` manager now rewrites only its own YAML section instead of dumping the whole file and stripping the generated version header. Regression coverage now proves the legacy helper keeps the generated header intact and that OpenAI-compatible connectors survive regeneration.
|
||||
- **2026-04-16**: **#1030** Browser automation is now a first-class CCS surface instead of an env-only/runtime-only feature. CCS adds `ccs help browser`, `ccs browser status`, and `ccs browser doctor`; a dedicated `Settings -> Browser` dashboard tab for Claude Browser Attach and Codex Browser Tools; a new `browser` section in `~/.ccs/config.yaml`; explicit readiness/next-step messaging for attach-mode Chrome sessions; and Codex UI guidance that marks the managed `ccs_browser` entry as CCS-owned and redirects browser setup away from the generic MCP editor.
|
||||
- **2026-04-15**: **#969** Local CLIProxy bootstrap no longer depends on live GitHub reachability during normal dashboard and runtime startup. CCS now skips hidden auto-update lookups on standard CLIProxy bootstrap paths, fails fast with explicit `ccs cliproxy install` guidance when a service start needs a binary that is not installed locally, and keeps `ccs config` able to open the dashboard in limited mode instead of stalling behind blocked release downloads.
|
||||
- **2026-04-15**: **#1010** Remote dashboard auth guidance now explains the Docker boundary explicitly. The readonly banner, remote login/setup card, and dashboard-auth docs now tell users that integrated Docker deployments keep config inside the running `ccs-cliproxy` container volume, so `ccs config auth setup` must run there rather than in the outer host shell.
|
||||
- **2026-04-14**: **#991** CCS now auto-routes Claude-target settings profiles that use OpenAI-compatible endpoints through a local Anthropic-compatible proxy instead of sending raw Anthropic `/v1/messages` traffic directly to chat-completions backends. The `ccs proxy` command now supports `start`, `status`, `activate`, and `stop` with explicit host binding, shell-aware activation helpers, and a fuller local runtime env contract. The proxy surface now exposes `GET /`, `/health`, `/v1/models`, and `/v1/messages`, logs routing decisions into CCS structured logs, supports Anthropic image blocks plus request-time `profile:model` overrides, and adds config-driven scenario routing (`background`, `think`, `longContext`, `webSearch`) on top of the compatible-profile path. Coverage now includes request routing, rate-limit/timeout/empty-upstream failures, chunked tool-call streaming, and disconnect cleanup alongside the existing unit, integration, and e2e suites.
|
||||
- **2026-04-10**: **#765** `/providers` now includes a first-class Hugging Face preset for API Profiles. CCS exposes Hugging Face Inference Providers through the existing OpenAI-compatible profile flow with the official router endpoint `https://router.huggingface.co/v1`, a short `hf` default profile name, and `hf` preset alias support for both the dashboard chooser and `ccs api create --preset hf`.
|
||||
- **2026-04-10**: **#944** Image Analysis auth readiness no longer collapses to native Read when merged runtime-status dependency overrides include a missing initializer value. CCS now preserves default dependency functions when override entries are `undefined`, still reads token-backed auth status directly in the local readiness path, and includes regression coverage for the missing-initializer case that previously surfaced as `deps.initializeAccounts is not a function`.
|
||||
- **2026-04-10**: **#945** CCS now normalizes Gemini CLI and Antigravity tier signals around an explicit `free / pro / ultra / unknown` model, preserves raw tier ids such as `g1-pro-tier`, enriches Gemini quota responses with provider entitlement evidence, classifies `MODEL_CAPACITY_EXHAUSTED` separately from auth/entitlement failures, fixes the Antigravity CLI quota table so live quota-derived tiers no longer collapse back to stale `unknown`, adds Gemini tier ids to CLI quota output, extends Gemini Flash Lite grouping to cover `gemini-3.1-flash-lite-preview`, and allows Gemini account surfaces to render the same tier badge semantics as Antigravity.
|
||||
- **2026-04-09**: **#938** Cliproxy model routing now exposes backend-pinned short prefixes for overlapping OAuth backends. CCS repairs managed OAuth auth-file prefixes for Gemini CLI (`gcli`) and Antigravity (`agy`), enriches `/api/cliproxy/catalog` with routing hints that show whether an unprefixed model is safe, shadowed, or prefix-only, upgrades `ccs cliproxy catalog` plus interactive variant model pickers to surface the pinned names, and updates the `ccs config` Cliproxy model selection UI so users can see the preferred call name and current effective backend before saving settings.
|
||||
- **2026-04-08**: **#931** `/cliproxy` model pickers now source their provider catalogs from CLIProxy management model definitions instead of treating the UI catalog file as the dropdown source of truth. CCS now refreshes live model definitions for Gemini, Codex, Claude, Antigravity, Qwen, iFlow, Kiro, GitHub Copilot, and Kimi through `/api/cliproxy/catalog`, overlays CCS-only preset/default metadata on top of those upstream models, keeps `/api/cliproxy/models` as the live availability feed, and falls back to cached/static catalogs when the proxy is unavailable so the dashboard never goes blank.
|
||||
- **2026-04-08**: **#929** Image Analysis hardening now makes the managed `ccs-image-analysis` MCP path authoritative on healthy Claude-target launches, suppresses stale CCS-managed image `Read` hooks instead of letting them compete with MCP, keeps the legacy hook available only as compatibility fallback when MCP provisioning fails, and extends self-heal to dashboard provisioning plus `ccs doctor --fix` so stale hook files and missing isolated MCP sync are repaired automatically.
|
||||
- **2026-04-07**: CLIProxy routing strategy is now a first-class CCS surface. Users can inspect and explicitly change `round-robin` vs `fill-first` from `ccs cliproxy routing` and from a native `/cliproxy` dashboard card. Local mode now persists the chosen startup default into CCS-managed CLIProxy config generation, while untouched installs remain on `round-robin`. CCS deliberately does not infer strategy from account composition.
|
||||
- **2026-04-07**: **#926** CCS now has a first-class structured logging layer under `src/services/logging/`, a bounded top-level `logging` config section in `~/.ccs/config.yaml`, automatic rotation/retention for CCS-owned logs under `~/.ccs/logs/`, native `/api/logs` dashboard endpoints, request tracing for the dashboard backend, and a dedicated `System -> Logs` dashboard route for browsing recent entries and editing retention settings. Legacy CLIProxy error files remain available as a labeled legacy source instead of acting as the primary logging model.
|
||||
- **2026-04-06**: The dashboard login surface now distinguishes a real sign-in from a host-setup requirement. Remote/IP visitors no longer see a misleading blank credential form when dashboard auth is disabled or incomplete; they now get explicit guidance that CCS has no default credentials, should be enabled on the host with `ccs config auth setup`, or should be reopened via localhost when used on the same machine. The password field now includes a show/hide toggle, and the page exposes an explicit light/dark theme switch before sign-in.
|
||||
- **2026-04-04**: The GitHub README was reduced from a wall-of-text reference dump into a shorter conversion surface that keeps the hero, proof screenshots, and fast-start commands while delegating deeper installation, provider, feature, and CLI-reference content to `docs.ccs.kaitran.ca`. The docs site now includes a dedicated `Product Tour` page for the screenshot-led walkthrough.
|
||||
- **2026-04-05**: **#912 #913 #914** Kiro auth is now aligned with the current CLIProxyAPIPlus contract. CCS auto-selects the Builder ID path for the default `ccs kiro --auth` flow instead of stalling on the upstream Builder ID vs IDC chooser, callback-based Kiro auth methods can use `--paste-callback` by replaying the pasted redirect URL back into the local callback server, and the CLI now supports IDC auth via `--kiro-auth-method idc` plus `--kiro-idc-start-url`, `--kiro-idc-region`, and `--kiro-idc-flow`.
|
||||
- **2026-04-03**: CCS CLI help and completion UX was refreshed. Root help is now shorter and task-oriented, `ccs help <topic|command>` routes to topic-aware help, and shell completions now delegate to the hidden `ccs __complete` backend.
|
||||
- **2026-04-02**: Third-party image and PDF analysis now follows the same first-class local-tool model as WebSearch. CCS provisions `ccs-image-analysis` as a managed MCP tool, routes requests directly to provider-scoped CCS endpoints such as `/api/provider/agy/v1/messages`, keeps editable prompt templates under `~/.ccs/prompts/image-analysis/`, and demotes the old `Read` hook to a best-effort compatibility fallback. Launches now stay non-fatal and fall back to native `Read` when the managed runtime cannot be prepared.
|
||||
- **2026-04-01**: The `Compatible -> Codex CLI` dashboard now exposes manual long-context controls for `model_context_window` and `model_auto_compact_token_limit`. CCS reads and patches those upstream Codex config keys directly, adds official guidance that GPT-5.4 long context is experimental and opt-in, and keeps the behavior manual-only so the dashboard never auto-fills or auto-saves long-context values for the user.
|
||||
- **2026-03-30**: **#862** Third-party WebSearch now uses a first-class CCS-managed MCP tool path instead of relying on a denied native Anthropic `WebSearch` call as the normal UX. CCS provisions `ccs-websearch` into `~/.claude.json`, syncs it into isolated account configs when needed, suppresses native `WebSearch` on third-party launches, preserves the provider order `Exa -> Tavily -> Brave -> DuckDuckGo -> legacy CLI fallback`, and keeps the old hook runtime only as shared provider plumbing plus compatibility fallback. Uninstall cleanup now also removes the managed WebSearch MCP runtime.
|
||||
- **2026-03-28**: **#773** CCS now ships a dedicated `Compatible -> Codex CLI` dashboard route with a real split-view control center. The page detects the local Codex binary, keeps overview/docs guidance, and adds guided editors for the user-owned `~/.codex/config.toml` layer: top-level runtime defaults, project trust, profiles, model providers, MCP servers, and supported feature flags. Structured saves intentionally normalize TOML formatting and drop comments, so the raw editor remains the fidelity escape hatch. Follow-up fixes added immediate raw snapshot refresh, refresh/discard recovery for stale raw drafts, dirty raw-editor guarding for structured controls, project-trust path validation, read-only handling for unreadable config files, preservation of unsupported upstream values such as granular `approval_policy`, and feature reset-to-default support. CCS still warns that transient runtime overrides such as `codex -c key=value` and `CCS_CODEX_API_KEY` may change effective behavior without persisting into the file.
|
||||
- **2026-03-27**: WebSearch dashboard cards now manage Exa, Tavily, and Brave API keys inline instead of relying on a separate manual env step. CCS stores those secrets through `global_env`, reflects masked key state in `/api/websearch`, and counts dashboard-managed keys as ready in the WebSearch status flow.
|
||||
- **2026-03-27**: **#812** CCS now includes a first-class `ccs docker` command suite for self-hosting the integrated Dashboard + CLIProxy stack. The CLI can stage bundled Docker assets locally or to a remote `--host` over SSH, report compose/supervisor status, stream CCS or CLIProxy logs, and run in-container update flows without relying on ad-hoc deployment scripts.
|
||||
- **2026-03-24**: Official Claude Channels now follow Anthropic's actual runtime contract. CCS blocks auto-enable unless Bun is available, Claude Code is verified at v2.1.80+, and `claude.ai` auth is verified; treats `--allow-dangerously-skip-permissions` as an explicit override; keeps Telegram/Discord bot tokens in Claude's shared `~/.claude/channels/` state (or official `*_STATE_DIR` overrides); and upgrades the dashboard/CLI status flow with Bun/version/auth/state-scope guidance, safer token draft retention on refresh failures, and a non-macOS iMessage toggle that can still be turned off when already selected.
|
||||
- **2026-03-23**: CLIProxy providers that do not expose an email no longer require a user-supplied nickname on first auth. CCS now derives a stable internal account identifier for Kiro/Copilot-style flows, preserves later rename support, hardens account discovery/registry sync around that identifier, and updates AI Provider CRUD to use stable entry IDs instead of dashboard list indexes.
|
||||
- **2026-03-23**: Sensitive dashboard management routes now fail closed to localhost-only access whenever dashboard auth is disabled. Remote access remains available after `ccs config auth setup`, but AI Provider management, CLIProxy auth/status helpers, and other write-capable settings endpoints no longer trust unauthenticated non-loopback requests.
|
||||
- **2026-03-19**: **#649** CCS splits CLIProxy provider-key authoring into a dedicated `CLIProxy -> AI Providers` dashboard route. `/cliproxy` now stays focused on OAuth accounts and variants, `/cliproxy/ai-providers` owns Gemini/Codex/Claude/Vertex/OpenAI-compatible key management, and `/providers` stays reserved for CCS-native API Profiles.
|
||||
- **2026-03-18**: **#755** Marketplace refresh no longer reuses one shared `known_marketplaces.json` across isolated instances. CCS now keeps marketplace payload directories shared while reconciling per-instance marketplace metadata so Claude Code validation succeeds for alternating or concurrent profiles, including Windows copy fallback.
|
||||
- **2026-03-17**: Deprecated user-facing GLMT discovery across CLI help, completions, presets, and docs. Existing `glmt` profiles now run through a compatibility path that normalizes legacy proxy settings to the direct GLM endpoint.
|
||||
- **#748**: API profile creation now keeps provider selection compact by collapsing advanced presets behind an explicit toggle, shrinking chooser cards so the form fields stay visually primary, and giving `llama.cpp` a dedicated provider logo.
|
||||
- **#744**: API profile creation now keeps featured providers in a horizontal rail with scroll fallback, moves Anthropic Direct API to the end, reuses the shared Claude logo, and separates the custom-endpoint entry point from advanced template discovery.
|
||||
- **#724**: Codex startup is now free-plan safe. CCS defaults new Codex sessions to a cross-plan model and uses runtime fallback handling for unsupported paid-only models without rewriting the saved dashboard settings.
|
||||
- **#737**: Dashboard model pickers in Cursor, Copilot, and CLIProxy now use a searchable combobox with autofocus and explicit no-results states for large model catalogs.
|
||||
- **#736**: `ccs config` now supports explicit dashboard bind hosts via `--host`, and surfaces remote-access warnings plus reachable URLs when the effective bind is non-loopback.
|
||||
- **#1121**: Usage analytics pricing now refreshes cached models.dev metadata before cost derivation, keeps CCS static pricing as the offline fallback, and carries provider identity through CLIProxy/native runtime breakdowns so subscription-backed providers do not inherit paid API pricing.
|
||||
CCS remains a CLI-first profile and runtime manager with dashboard parity for
|
||||
configuration. New work should preserve:
|
||||
|
||||
### Maintainability Hardening Kickoff
|
||||
|
||||
- Issue owner: Stream D for **#542**
|
||||
- Automated inventory command: `bun run report:hardening`
|
||||
- Generated report artifacts:
|
||||
- `docs/reports/hardening-inventory.json`
|
||||
- `docs/reports/hardening-inventory.md`
|
||||
- Debt burndown tracker: [Hardening Debt Burndown Tracker](./hardening-debt-burndown.md)
|
||||
|
||||
### Remaining Large Files (Acceptable)
|
||||
|
||||
**CLI** (complex core logic):
|
||||
- `model-pricing.ts` (676 lines) - Data file
|
||||
- `glmt-proxy.ts` (675 lines) - Legacy internal compatibility proxy
|
||||
- `cliproxy-executor.ts` (666 lines) - Core execution
|
||||
- `ccs.ts` (596 lines) - Entry point
|
||||
|
||||
**UI** (external/shadcn):
|
||||
- `components/ui/sidebar.tsx` (674 lines) - shadcn component
|
||||
|
||||
---
|
||||
|
||||
## GitHub Issues Backlog
|
||||
|
||||
### Critical (Blocking Users)
|
||||
|
||||
| Issue | Title | Type |
|
||||
|-------|-------|------|
|
||||
| #158 | AGY not working - Missing API Key - Run /login | bug |
|
||||
| #155 | Invalid JSON payload error with Gemini/Antigravity | bug |
|
||||
| #124 | Incorrect model ID for Claude 3.5 Sonnet (Thinking) | bug |
|
||||
|
||||
### High Priority (Features)
|
||||
|
||||
| Issue | Title | Type | Status |
|
||||
|-------|-------|------|--------|
|
||||
| #142 | Configure with available CLIProxyAPI | enhancement | **COMPLETE** (v7.1) |
|
||||
| #157 | Support for Kiro auth from CLIProxyAPIPlus | enhancement | **COMPLETE** (v7.2) |
|
||||
| #123 | Add More Models | enhancement | Ongoing |
|
||||
| #114 | OpenCode Zen Free model + Auto Rotation API Key | enhancement | - |
|
||||
|
||||
### Medium Priority
|
||||
|
||||
| Issue | Title | Type |
|
||||
|-------|-------|------|
|
||||
| #137 | CCS Cannot Connect to IDE, but Native Claude Works | support |
|
||||
| #89 | Add Claude Code CLI flag passthrough for delegation | enhancement |
|
||||
| #659 | Comprehensive Vietnamese i18n for dashboard | enhancement |
|
||||
|
||||
### Low Priority / Questions
|
||||
|
||||
| Issue | Title | Type |
|
||||
|-------|-------|------|
|
||||
| #156 | Configure API for Zed IDE | docs |
|
||||
| #140 | Do we support ampcode? | question |
|
||||
| #111 | Factory droid CLI support | enhancement |
|
||||
| #103 | /context command returns incorrect context | invalid |
|
||||
|
||||
---
|
||||
|
||||
## Future Roadmap
|
||||
|
||||
### Priority 1: Multiple CLIProxyAPI Instances
|
||||
|
||||
Support connecting to multiple CLIProxyAPI servers simultaneously.
|
||||
|
||||
**Use Cases**:
|
||||
- Load balancing across multiple proxy servers
|
||||
- Failover when primary server unavailable
|
||||
- Geographic distribution for latency optimization
|
||||
- Separate proxies for different provider groups
|
||||
|
||||
**Proposed Config**:
|
||||
```yaml
|
||||
cliproxy:
|
||||
instances:
|
||||
primary:
|
||||
url: http://localhost:8000
|
||||
providers: [gemini, codex]
|
||||
weight: 80
|
||||
secondary:
|
||||
url: http://192.168.1.100:8000
|
||||
providers: [agy]
|
||||
weight: 20
|
||||
failover:
|
||||
url: http://backup.example.com:8000
|
||||
priority: 2 # Only if primary/secondary fail
|
||||
strategy: weighted-round-robin
|
||||
```
|
||||
|
||||
### Priority 2: Native Git Worktree Support
|
||||
|
||||
Opt-in automatic git worktree management for features/issues.
|
||||
|
||||
**Use Cases**:
|
||||
- Automatic worktree creation when starting issue
|
||||
- Isolation of feature development
|
||||
- Easy cleanup after merge
|
||||
- Integration with GitHub issues
|
||||
|
||||
**Proposed Settings**:
|
||||
```yaml
|
||||
worktrees:
|
||||
enabled: true
|
||||
base_path: ~/.ccs/worktrees
|
||||
auto_create: true
|
||||
auto_cleanup: true
|
||||
naming: "{issue-number}-{short-title}"
|
||||
```
|
||||
|
||||
### Priority 3: Enhanced Model Support
|
||||
|
||||
- **#123**: Expand model catalog with new releases
|
||||
- **#124**: Fix Claude 3.5 Sonnet (Thinking) model ID
|
||||
- **#114**: OpenCode Zen free model + API key rotation
|
||||
|
||||
### Priority 4: IDE Integration
|
||||
|
||||
- **#137**: Debug CCS-to-IDE connection issues
|
||||
- **#156**: Zed IDE configuration documentation
|
||||
- **#140**: Investigate ampcode compatibility
|
||||
- **#111**: Factory droid CLI support assessment
|
||||
|
||||
### Priority 5: Authentication Enhancements
|
||||
|
||||
- **#158**: Fix AGY OAuth flow
|
||||
- **#157**: ~~Add Kiro auth support from CLIProxyAPIPlus~~ **COMPLETE** (v7.2)
|
||||
- GitHub Copilot (ghcp) Device Code flow **COMPLETE** (v7.2, now deprecated compatibility)
|
||||
- Hybrid quota management **COMPLETE** (v7.14)
|
||||
|
||||
---
|
||||
|
||||
## Milestones
|
||||
|
||||
| Milestone | Status | Target |
|
||||
|-----------|--------|--------|
|
||||
| Modularization (Phases 1-9) | COMPLETE | - |
|
||||
| Remote CLIProxy Support (#142) | COMPLETE | v7.1 |
|
||||
| Kiro + GitHub Copilot OAuth (#157) | COMPLETE, Copilot now deprecated compatibility | v7.2 |
|
||||
| Hybrid Quota Management | COMPLETE | v7.14 |
|
||||
| Docker Support (PR #345) | COMPLETE | v7.23 |
|
||||
| Image Analysis Hook | COMPLETE | v7.34 |
|
||||
| Third-Party Tool Integration | COMPLETE | v7.39 |
|
||||
| Critical Bug Fixes (#158, #155, #124) | PLANNED | Q1 2026 |
|
||||
| Multiple CLIProxyAPI Instances | PLANNED | Q1 2026 |
|
||||
| Git Worktree Support | PLANNED | Q2 2026 |
|
||||
| Enhanced Model Support | PLANNED | Q2 2026 |
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
All criteria achieved:
|
||||
|
||||
- [x] Files under 200 lines (except documented exceptions)
|
||||
- [x] Every directory has barrel export
|
||||
- [x] No circular dependencies
|
||||
- [x] TypeScript strict mode passing
|
||||
- [x] 90%+ test coverage
|
||||
- [x] Clear domain boundaries
|
||||
- [x] Consistent naming conventions
|
||||
|
||||
## Historical Maintainability Gate (Retired)
|
||||
|
||||
This section is preserved as historical context from the original Issue `#539` work.
|
||||
|
||||
- The maintainability baseline gate is no longer part of the active CCS workflow.
|
||||
- Current contributor and CI gates are documented in `CLAUDE.md`, `CONTRIBUTING.md`, and the GitHub workflow files.
|
||||
- Do not assume `maintainability:baseline` or `maintainability:check` exist unless they are reintroduced in a future follow-up.
|
||||
|
||||
---
|
||||
- isolated account/profile state;
|
||||
- explicit provider and runtime selection;
|
||||
- actionable, ASCII-safe terminal errors;
|
||||
- compatible CLI, dashboard, and public-doc contracts;
|
||||
- safe local defaults for credentials, browser access, and proxy routing.
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Codebase Summary](./codebase-summary.md) - Current structure
|
||||
- [Code Standards](./code-standards.md) - Patterns and conventions
|
||||
- [System Architecture](./system-architecture/index.md) - Architecture diagrams
|
||||
- [Hardening Debt Burndown Tracker](./hardening-debt-burndown.md) - Legacy shim + sync-fs debt tracking
|
||||
- [CLAUDE.md](../CLAUDE.md) - AI development guidance
|
||||
- [Codebase Summary](./codebase-summary.md)
|
||||
- [Code Standards](./code-standards.md)
|
||||
- [System Architecture](./system-architecture/index.md)
|
||||
- [CLAUDE.md](../CLAUDE.md)
|
||||
@@ -1,118 +1,11 @@
|
||||
{
|
||||
"scope": "src/**/*.{ts,tsx,js,jsx,mjs,cjs}",
|
||||
"syncFs": {
|
||||
"totalOccurrences": 2358,
|
||||
"filesAffected": 253,
|
||||
"hotpathOccurrences": 1991,
|
||||
"hotpathFilesAffected": 202,
|
||||
"totalOccurrences": 2378,
|
||||
"filesAffected": 254,
|
||||
"hotpathOccurrences": 1102,
|
||||
"hotpathFilesAffected": 150,
|
||||
"topHotpathFiles": [
|
||||
{
|
||||
"file": "src/cliproxy/__tests__/pool-routing-phase3.test.ts",
|
||||
"count": 96,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"mkdtempSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/__tests__/config-generator.test.js",
|
||||
"count": 88,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"mkdtempSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/__tests__/claude-model-neutral.test.ts",
|
||||
"count": 63,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"mkdtempSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"statSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/accounts/__tests__/account-safety-quota-exhaustion.test.ts",
|
||||
"count": 48,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"mkdtempSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/accounts/__tests__/account-registry-integrity.test.ts",
|
||||
"count": 45,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"mkdtempSync",
|
||||
"readdirSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/executor/__tests__/variant-port-integration.test.js",
|
||||
"count": 36,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readdirSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/executor/__tests__/composite-variant-service.test.ts",
|
||||
"count": 33,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"mkdtempSync",
|
||||
"readFileSync",
|
||||
"rmSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/executor/__tests__/variant-port-edge-cases.test.js",
|
||||
"count": 33,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readdirSync",
|
||||
"rmSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/utils/browser/mcp-installer.ts",
|
||||
"count": 32,
|
||||
@@ -130,14 +23,136 @@
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/__tests__/session-tracker-port.test.js",
|
||||
"count": 31,
|
||||
"file": "src/utils/image-analysis/mcp-installer.ts",
|
||||
"count": 30,
|
||||
"calls": [
|
||||
"chmodSync",
|
||||
"copyFileSync",
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readFileSync",
|
||||
"renameSync",
|
||||
"statSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/utils/claude-symlink-manager.ts",
|
||||
"count": 27,
|
||||
"calls": [
|
||||
"copyFileSync",
|
||||
"existsSync",
|
||||
"lstatSync",
|
||||
"mkdirSync",
|
||||
"readdirSync",
|
||||
"readlinkSync",
|
||||
"renameSync",
|
||||
"rmSync",
|
||||
"statSync",
|
||||
"symlinkSync",
|
||||
"unlinkSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/env-builder.ts",
|
||||
"count": 25,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readdirSync",
|
||||
"readFileSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/management/shared-manager/migrations.ts",
|
||||
"count": 25,
|
||||
"calls": [
|
||||
"copyFileSync",
|
||||
"cpSync",
|
||||
"existsSync",
|
||||
"lstatSync",
|
||||
"mkdirSync",
|
||||
"readdirSync",
|
||||
"symlinkSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/utils/websearch/mcp-installer.ts",
|
||||
"count": 25,
|
||||
"calls": [
|
||||
"chmodSync",
|
||||
"copyFileSync",
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readFileSync",
|
||||
"renameSync",
|
||||
"statSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/services/variant-settings.ts",
|
||||
"count": 23,
|
||||
"calls": [
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readFileSync",
|
||||
"renameSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/management/recovery-manager.ts",
|
||||
"count": 23,
|
||||
"calls": [
|
||||
"copyFileSync",
|
||||
"existsSync",
|
||||
"lstatSync",
|
||||
"mkdirSync",
|
||||
"renameSync",
|
||||
"statSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/utils/shell-completion.ts",
|
||||
"count": 23,
|
||||
"calls": [
|
||||
"appendFileSync",
|
||||
"copyFileSync",
|
||||
"existsSync",
|
||||
"mkdirSync",
|
||||
"readFileSync",
|
||||
"statSync"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"file": "src/utils/claude-dir-installer.ts",
|
||||
"count": 21,
|
||||
"calls": [
|
||||
"copyFileSync",
|
||||
"cpSync",
|
||||
"existsSync",
|
||||
"lstatSync",
|
||||
"mkdirSync",
|
||||
"readdirSync",
|
||||
"renameSync",
|
||||
"rmSync",
|
||||
"statSync",
|
||||
"unlinkSync",
|
||||
"writeFileSync"
|
||||
],
|
||||
@@ -285,8 +300,8 @@
|
||||
]
|
||||
},
|
||||
"legacyShim": {
|
||||
"totalMarkers": 454,
|
||||
"filesAffected": 170,
|
||||
"totalMarkers": 456,
|
||||
"filesAffected": 171,
|
||||
"topFiles": [
|
||||
{
|
||||
"file": "src/auth/profile-detector.ts",
|
||||
@@ -476,11 +491,11 @@
|
||||
},
|
||||
"maintainability": {
|
||||
"typedErrors": {
|
||||
"totalThrows": 440,
|
||||
"typedThrows": 68,
|
||||
"totalThrows": 446,
|
||||
"typedThrows": 74,
|
||||
"plainThrows": 312,
|
||||
"otherThrows": 60,
|
||||
"adoptionRatio": 0.1545,
|
||||
"adoptionRatio": 0.1659,
|
||||
"topSubdomainsByThrows": [
|
||||
{
|
||||
"subdomain": "web-server",
|
||||
@@ -558,8 +573,8 @@
|
||||
},
|
||||
"loggerCoverage": {
|
||||
"filesWithCreateLogger": 65,
|
||||
"totalSourceFiles": 751,
|
||||
"coverageRatio": 0.0866,
|
||||
"totalSourceFiles": 752,
|
||||
"coverageRatio": 0.0864,
|
||||
"subdomainsWithZeroCreateLogger": [
|
||||
"api",
|
||||
"bin",
|
||||
@@ -728,7 +743,11 @@
|
||||
},
|
||||
{
|
||||
"file": "src/web-server/routes/settings-routes.ts",
|
||||
"loc": 1041
|
||||
"loc": 1042
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/generator.ts",
|
||||
"loc": 1034
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/proxy/tool-sanitization-proxy.ts",
|
||||
@@ -738,10 +757,6 @@
|
||||
"file": "src/cliproxy/config/env-builder.ts",
|
||||
"loc": 1017
|
||||
},
|
||||
{
|
||||
"file": "src/cliproxy/config/generator.ts",
|
||||
"loc": 1012
|
||||
},
|
||||
{
|
||||
"file": "src/commands/cliproxy/variant-subcommand.ts",
|
||||
"loc": 997
|
||||
|
||||
@@ -6,27 +6,27 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
||||
|
||||
| Metric | Value |
|
||||
|---|---:|
|
||||
| Sync fs occurrences (all) | 2358 |
|
||||
| Sync fs files affected (all) | 253 |
|
||||
| Sync fs occurrences (runtime hotpaths) | 1991 |
|
||||
| Sync fs files affected (runtime hotpaths) | 202 |
|
||||
| Legacy shim markers | 454 |
|
||||
| Legacy shim files affected | 170 |
|
||||
| Sync fs occurrences (all) | 2378 |
|
||||
| Sync fs files affected (all) | 254 |
|
||||
| Sync fs occurrences (runtime hotpaths) | 1102 |
|
||||
| Sync fs files affected (runtime hotpaths) | 150 |
|
||||
| Legacy shim markers | 456 |
|
||||
| Legacy shim files affected | 171 |
|
||||
|
||||
## Top Runtime Hotpath Sync fs Files
|
||||
|
||||
| File | Sync Calls | API Names |
|
||||
|---|---:|---|
|
||||
| `src/cliproxy/__tests__/pool-routing-phase3.test.ts` | 96 | existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync |
|
||||
| `src/cliproxy/config/__tests__/config-generator.test.js` | 88 | existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync |
|
||||
| `src/cliproxy/config/__tests__/claude-model-neutral.test.ts` | 63 | existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync |
|
||||
| `src/cliproxy/accounts/__tests__/account-safety-quota-exhaustion.test.ts` | 48 | existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync |
|
||||
| `src/cliproxy/accounts/__tests__/account-registry-integrity.test.ts` | 45 | existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync |
|
||||
| `src/cliproxy/executor/__tests__/variant-port-integration.test.js` | 36 | existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync |
|
||||
| `src/cliproxy/executor/__tests__/composite-variant-service.test.ts` | 33 | existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync |
|
||||
| `src/cliproxy/executor/__tests__/variant-port-edge-cases.test.js` | 33 | existsSync, mkdirSync, readdirSync, rmSync, unlinkSync, writeFileSync |
|
||||
| `src/utils/browser/mcp-installer.ts` | 32 | chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync |
|
||||
| `src/cliproxy/__tests__/session-tracker-port.test.js` | 31 | existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync |
|
||||
| `src/utils/image-analysis/mcp-installer.ts` | 30 | chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync |
|
||||
| `src/utils/claude-symlink-manager.ts` | 27 | copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync |
|
||||
| `src/cliproxy/config/env-builder.ts` | 25 | existsSync, mkdirSync, readFileSync, writeFileSync |
|
||||
| `src/management/shared-manager/migrations.ts` | 25 | copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, symlinkSync, unlinkSync, writeFileSync |
|
||||
| `src/utils/websearch/mcp-installer.ts` | 25 | chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync |
|
||||
| `src/cliproxy/services/variant-settings.ts` | 23 | existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync |
|
||||
| `src/management/recovery-manager.ts` | 23 | copyFileSync, existsSync, lstatSync, mkdirSync, renameSync, statSync, unlinkSync, writeFileSync |
|
||||
| `src/utils/shell-completion.ts` | 23 | appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync |
|
||||
| `src/utils/claude-dir-installer.ts` | 21 | copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync |
|
||||
|
||||
## Top Legacy Shim Marker Files
|
||||
|
||||
@@ -56,11 +56,11 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
||||
|
||||
| Metric | Value |
|
||||
|---|---:|
|
||||
| typed-error adoption (typed/total throws) | 15.4% (68/440) |
|
||||
| typed-error adoption (typed/total throws) | 16.6% (74/446) |
|
||||
| typed-error adoption (P4 locked subdomains) | 91.7% (22/24), target 40% |
|
||||
| hotpath console.error/warn occurrences | 266 (571 total, 305 CLI-UX exempt) |
|
||||
| hotpath console.error/warn files | 82 |
|
||||
| files with createLogger | 65/751 |
|
||||
| files with createLogger | 65/752 |
|
||||
| subdomains with zero createLogger | 15 (api, bin, channels, cliproxy, cliproxy/accounts, cliproxy/ai-providers, cliproxy/binary, cliproxy/config, cliproxy/management, cliproxy/sync, cliproxy/types, config, dispatcher, shared, types) |
|
||||
| files > 400 LOC | 91 |
|
||||
| files > 600 LOC | 42 |
|
||||
@@ -95,10 +95,10 @@ Scope: `src/**/*.{ts,tsx,js,jsx,mjs,cjs}`
|
||||
| `src/cursor/cursor-executor.ts` | 1234 |
|
||||
| `src/web-server/model-pricing.ts` | 1105 |
|
||||
| `src/cliproxy/auth/oauth-process.ts` | 1048 |
|
||||
| `src/web-server/routes/settings-routes.ts` | 1041 |
|
||||
| `src/web-server/routes/settings-routes.ts` | 1042 |
|
||||
| `src/cliproxy/config/generator.ts` | 1034 |
|
||||
| `src/cliproxy/proxy/tool-sanitization-proxy.ts` | 1020 |
|
||||
| `src/cliproxy/config/env-builder.ts` | 1017 |
|
||||
| `src/cliproxy/config/generator.ts` | 1012 |
|
||||
| `src/commands/cliproxy/variant-subcommand.ts` | 997 |
|
||||
| `src/cliproxy/quota/quota-manager.ts` | 954 |
|
||||
| `src/web-server/services/codex-dashboard-service.ts` | 940 |
|
||||
|
||||
@@ -1,26 +1,36 @@
|
||||
# Typed-Error Exit-Code Compat Audit (P4)
|
||||
# Typed-Error Exit-Code Compatibility Audit
|
||||
|
||||
Date: 2026-06-18. Phase 4 of the maintainability/traceability epic.
|
||||
Status: resolved contract.
|
||||
|
||||
## Question (open Q1)
|
||||
## Question
|
||||
|
||||
Are CCS CLI exit codes a documented public contract that users or CI scripts depend on? This determines whether migrating `throw new Error(...)` to typed errors (which changes the exit code) is safe.
|
||||
|
||||
## Finding
|
||||
|
||||
Typed exit codes are **already wired end-to-end**. `handleError` -> `getExitCode` extracts `error.code` from any `CCSError` and passes it to `process.exit` (`src/errors/error-handler.ts`, `src/ccs.ts:145`). The `ExitCode` enum and the class-to-code mapping in `src/errors/error-types.ts` are complete and pre-date this epic.
|
||||
Typed exit codes are wired end-to-end. `handleError` calls the local
|
||||
`getExitCode` helper and passes the resulting code to `process.exit` in
|
||||
`src/errors/error-handler.ts`. The CLI entry point delegates uncaught errors to
|
||||
`handleError` in `src/ccs.ts`. `src/errors/exit-codes.ts` and
|
||||
`src/errors/error-types.ts` define the stable mapping.
|
||||
|
||||
**Only documented public contract:** `ccs doctor` documents exit codes 0 (healthy) / 1 (unhealthy) in `src/commands/doctor-command.ts:55-58`. `ccs doctor` is OUTSIDE the P4 priority subdomains and is NOT touched by P4. Its 0/1 contract is preserved.
|
||||
`ccs doctor` separately documents exit codes 0 (healthy) and 1 (unhealthy) in
|
||||
`src/commands/doctor-command.ts`. Preserve that command contract.
|
||||
|
||||
**No CI/scripts assert on ccs exit codes.** `scripts/ci-parity-gate.sh` uses `set -euo pipefail` but performs no `ccs` exit-code branching. `.github/workflows/*` perform no ccs exit-code assertions. Prior exit-code changes in CHANGELOG are treated as bugfixes; no documented breaking changes.
|
||||
The typed taxonomy is enforced by
|
||||
`src/errors/__tests__/typed-error-migration-exit-codes.test.ts`; do not treat
|
||||
the mapping as an untested migration note.
|
||||
|
||||
## Decision
|
||||
|
||||
**Migrate freely** in the P4 priority subdomains (`cliproxy/quota`, `cliproxy/auth`, `web-server/routes`, `auth`). Preserve `GENERAL_ERROR(1)` only where no clear subclass applies. Behavior-lock tests assert the new typed codes.
|
||||
Use the most specific typed error in `cliproxy/quota`, `cliproxy/auth`,
|
||||
`web-server/routes`, and `auth`. Preserve `GENERAL_ERROR(1)` only where no clear
|
||||
subclass applies. Update the behavior-lock test with any intentional taxonomy
|
||||
change.
|
||||
|
||||
## Exit-code mapping (the contract this audit locks)
|
||||
|
||||
| Typed class | ExitCode | Value | Used for (P4 sites) |
|
||||
| Typed class | ExitCode | Value | Typical use |
|
||||
|---|---|---:|---|
|
||||
| `ProfileError` | `PROFILE_ERROR` | 7 | profile/account/variant not found, already exists |
|
||||
| `AuthError` | `AUTH_ERROR` | 4 | OAuth/token/Kiro/GitLab auth flow failures, refresh ownership |
|
||||
@@ -31,15 +41,5 @@ Typed exit codes are **already wired end-to-end**. `handleError` -> `getExitCode
|
||||
| `ProxyError` | `PROXY_ERROR` | 8 | |
|
||||
| `MigrationError` | `MIGRATION_ERROR` | 9 | |
|
||||
|
||||
## Per-site decisions
|
||||
|
||||
See the P4 commit for the full site list. Summary by subclass chosen:
|
||||
- `ProfileError`: profile/account not-found + already-exists (`src/auth/profile-registry.ts`, `src/cliproxy/auth/auth-token-manager.ts`, `src/cliproxy/auth/auth-types.ts`).
|
||||
- `AuthError`: OAuth start failed, paste-callback unavailable, Kiro auth method unsupported, token refresh ownership (`src/cliproxy/auth/oauth-handler.ts`, `provider-refreshers/index.ts`).
|
||||
- `ConfigError`: invalid settings path, settings not found, CLIProxy config not initialized, GitLab URL format, failed read/write profiles, copilot sync failure (`src/web-server/routes/*`, `src/cliproxy/auth/oauth-handler.ts`, `src/auth/profile-registry.ts`).
|
||||
- `ValidationError`: invalid profile name, invalid target, invalid host, Kiro IDC start-url (`src/web-server/routes/*`, `src/cliproxy/auth/auth-types.ts`).
|
||||
- `ProviderError`: unsupported provider backend (`src/web-server/routes/image-analysis-routes.ts`).
|
||||
|
||||
## Outcome
|
||||
|
||||
Typed-error adoption in the locked subdomains: 0/23 -> 21/23 (91.3%), well above the 40% target. Exit-code changes are intentional and documented here; release notes for the epic PR should mention the differentiated exit codes.
|
||||
Current adoption counts belong in the generated hardening inventory. Git history
|
||||
contains the completed migration narrative.
|
||||
@@ -1,216 +0,0 @@
|
||||
# Session Sharing Technical Analysis
|
||||
|
||||
Last Updated: 2026-05-05
|
||||
|
||||
## Summary
|
||||
|
||||
CCS supports practical cross-account continuity by sharing workspace context files between selected accounts, while keeping credentials isolated per account.
|
||||
|
||||
This is implemented as a context policy per account:
|
||||
|
||||
- `isolated` (default): account keeps its own workspace context
|
||||
- `shared` + `standard` (default): account workspace context is linked to a shared context group
|
||||
- `shared` + `deeper` (advanced opt-in): account also shares continuity artifacts
|
||||
|
||||
## Recommended Two-Account Route
|
||||
|
||||
Use `ccs auth` account profiles when you want two real Claude accounts and want to choose which one runs each session:
|
||||
|
||||
```bash
|
||||
ccs auth create work
|
||||
ccs auth create personal
|
||||
|
||||
ccs work
|
||||
ccs personal
|
||||
```
|
||||
|
||||
This keeps usage and credentials isolated. Each account owns its own Claude config directory, login state, and `.anthropic` credentials.
|
||||
|
||||
Shared Resources are separate from History Sync. By default, non-bare account profiles inherit Claude-local resources from native Claude:
|
||||
|
||||
```text
|
||||
~/.ccs/instances/<account>/settings.json
|
||||
-> ~/.ccs/shared/settings.json
|
||||
-> ~/.claude/settings.json
|
||||
```
|
||||
|
||||
This covers ordinary Claude Code `settings.json`, commands, skills, agents, and plugins. It is not token sharing. `ccs auth show <account>` reports `Resources`, `Settings`, `History`, and `Plain ccs` lanes so users can see whether shared resources and resume history are aligned.
|
||||
|
||||
For existing accounts, change Shared Resources from the CLI:
|
||||
|
||||
```bash
|
||||
ccs auth resources work --mode profile-local
|
||||
ccs auth resources work --mode shared
|
||||
```
|
||||
|
||||
- `shared`: link plugins, commands, skills, agents, and `settings.json` from the shared Claude resource layout.
|
||||
- `profile-local`: detach those shared resources for the account. This is the existing `--bare` behavior exposed as an existing-account setting.
|
||||
|
||||
Only opt in to shared history when both accounts should see the same local continuity:
|
||||
|
||||
```bash
|
||||
ccs auth create work2 --share-context --context-group daily --deeper-continuity
|
||||
```
|
||||
|
||||
For existing History Sync, use Dashboard -> Accounts -> Sync on both accounts, set both to `shared`, and use the same `History Sync Group`. Use `deeper` only when users expect stronger local handoff beyond project context. History Sync does not control plugins or `settings.json`; use `ccs auth resources` for that.
|
||||
|
||||
## Why This Is Safe Enough
|
||||
|
||||
CCS only shares workspace context paths (project/session context files). It does **not** merge or copy authentication credentials between accounts.
|
||||
|
||||
Credential storage remains per account instance.
|
||||
|
||||
## Implementation Model
|
||||
|
||||
Account metadata is stored in `~/.ccs/config.yaml`:
|
||||
|
||||
```yaml
|
||||
accounts:
|
||||
work:
|
||||
created: "2026-02-24T00:00:00.000Z"
|
||||
last_used: null
|
||||
shared_resource_mode: "shared"
|
||||
context_mode: "shared"
|
||||
context_group: "team-alpha"
|
||||
continuity_mode: "deeper"
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `shared_resource_mode` controls commands, skills, agents, plugins, and `settings.json` (`shared` or `profile-local`)
|
||||
- `context_mode` must be `isolated` or `shared`
|
||||
- `context_group` is required when `context_mode=shared`
|
||||
- `continuity_mode` is valid only when `context_mode=shared` (`standard` or `deeper`)
|
||||
- group normalization: trim, lowercase, internal spaces -> `-`
|
||||
- group must start with a letter and only include `[a-zA-Z0-9_-]`
|
||||
- max length: `64`
|
||||
|
||||
Deeper continuity links these directories per context group:
|
||||
|
||||
- `session-env`
|
||||
- `file-history`
|
||||
- `shell-snapshots`
|
||||
- `todos`
|
||||
|
||||
`.anthropic` and account credentials remain isolated.
|
||||
|
||||
## Cross-Profile Inheritance (API / CLIProxy / Copilot)
|
||||
|
||||
You can explicitly map non-account profiles (including `default`) to reuse continuity artifacts from an account profile:
|
||||
|
||||
```yaml
|
||||
continuity:
|
||||
inherit_from_account:
|
||||
glm: pro
|
||||
gemini: pro
|
||||
copilot: pro
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- Applies only when running Claude target (`ccs <profile>` or `--target claude`)
|
||||
- Does not change provider credentials or API routing
|
||||
- Reuses `CLAUDE_CONFIG_DIR` from mapped account profile after normal account context policy resolution
|
||||
- Invalid/missing mapped accounts are skipped safely
|
||||
|
||||
### Resume Lane Note
|
||||
|
||||
Resume follows the active `CLAUDE_CONFIG_DIR`, not just the continuity group:
|
||||
|
||||
- plain `ccs -r` resumes the lane plain `ccs` is using right now
|
||||
- `ccs <account> -r` resumes only that account lane
|
||||
- those two commands can point at different continuity inventories
|
||||
|
||||
That means `shared + deeper` on an account does **not** automatically make old plain-`ccs` resume history appear inside `ccs <account> -r`.
|
||||
|
||||
If you want future plain `ccs` sessions to use an account lane, either:
|
||||
|
||||
```bash
|
||||
ccs auth default work
|
||||
```
|
||||
|
||||
or map the default profile explicitly:
|
||||
|
||||
```yaml
|
||||
continuity:
|
||||
inherit_from_account:
|
||||
default: work
|
||||
```
|
||||
|
||||
Example with an existing `ck` account:
|
||||
|
||||
```bash
|
||||
ccs auth show ck
|
||||
ccs auth backup default
|
||||
ccs auth default ck
|
||||
```
|
||||
|
||||
`ccs auth default ck` makes future plain `ccs` sessions use the `ck` account lane, so future `ccs` and `ccs ck` resume from the same local inventory. It does not automatically import old native `~/.claude/projects` history into `ck`; keep using `ccs -r` for the old native lane until you intentionally migrate that local history.
|
||||
|
||||
## User Workflows
|
||||
|
||||
### New account with shared context
|
||||
|
||||
```bash
|
||||
ccs auth create work2 --share-context
|
||||
ccs auth create backup --share-context --context-group sprint-a
|
||||
ccs auth create backup2 --share-context --context-group sprint-a --deeper-continuity
|
||||
```
|
||||
|
||||
### Existing account
|
||||
|
||||
History Sync:
|
||||
|
||||
- Open `ccs config`
|
||||
- Go to `Accounts`
|
||||
- Click the pencil icon (`Edit History Sync`)
|
||||
- Choose `isolated` or `shared`, set group, and (optionally) choose deeper continuity
|
||||
|
||||
Shared Resources:
|
||||
|
||||
```bash
|
||||
ccs auth resources work --mode profile-local
|
||||
ccs auth resources work --mode shared
|
||||
```
|
||||
|
||||
Dashboard:
|
||||
|
||||
- Open `ccs config`
|
||||
- Go to `Accounts`
|
||||
- Use `Resources` to switch an existing account between `shared` and `profile-local`
|
||||
- Go to `Shared Resources` to inspect the shared commands, skills, agents, plugins, and `settings.json` hub
|
||||
|
||||
No account recreation required for this workflow.
|
||||
|
||||
### Backup Before Changing Sync
|
||||
|
||||
CCS can back up local continuity artifacts before you change settings:
|
||||
|
||||
```bash
|
||||
ccs auth backup work
|
||||
ccs auth backup default
|
||||
```
|
||||
|
||||
- `ccs auth backup work` backs up the selected account lane
|
||||
- `ccs auth backup default` backs up the lane plain `ccs` would use right now
|
||||
- this is a local continuity backup, not a guaranteed export of all upstream Claude-hosted resume state
|
||||
|
||||
## Current Limitations
|
||||
|
||||
- Shared context is local filesystem sharing. It does not bypass remote provider permission models.
|
||||
- Session continuity still depends on what the upstream tool/provider stores and allows.
|
||||
- Context sharing should only be enabled for accounts you intentionally trust to share workspace history.
|
||||
- Shared Resources inspection is read-only in the dashboard. Editing individual files still belongs to the owning command, skill, plugin, or settings surface.
|
||||
|
||||
## Alternative: CLIProxy Claude Pool
|
||||
|
||||
For users who prefer lower manual account switching, use CLIProxy Claude pool instead:
|
||||
|
||||
- Authenticate pool accounts via `ccs cliproxy auth claude`
|
||||
- Manage account pool behavior in `ccs config` -> `CLIProxy Plus`
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- Confirm account row shows `shared (<group>)` in Dashboard Accounts table
|
||||
- Switch between accounts in the same group and verify workspace continuity
|
||||
- Run `ccs doctor` if symlink/context health looks inconsistent
|
||||
@@ -44,3 +44,29 @@ the first time via right-click then Open, or clear quarantine with
|
||||
`xattr -dr com.apple.quarantine "/Applications/CCS Bar.app"`. Developer ID
|
||||
signing + notarization (`CCS_BAR_SIGNING=developer-id`) is the public-launch
|
||||
path and is not required for ad-hoc distribution.
|
||||
|
||||
The app ships as `CCS-Bar.app.zip` on the floating `ccs-bar-latest` GitHub
|
||||
release. `macos-bar/VERSION` is the single semver source; bump it in the same PR
|
||||
when a new app version is required.
|
||||
|
||||
### Automatic release
|
||||
|
||||
`.github/workflows/bar-release.yml` publishes the asset after a push to `main`
|
||||
that changes `macos-bar/**`, or from a manual `workflow_dispatch` run whose
|
||||
selected ref is `main`. It runs on the dedicated self-hosted macOS runner
|
||||
labelled `ccs-bar`.
|
||||
|
||||
### Manual fallback
|
||||
|
||||
From a macOS machine with Swift and `gh`:
|
||||
|
||||
```bash
|
||||
cd macos-bar
|
||||
./Scripts/package_app.sh
|
||||
gh release upload ccs-bar-latest dist/CCS-Bar.app.zip --clobber
|
||||
```
|
||||
|
||||
Pass a version to `package_app.sh` only when intentionally overriding
|
||||
`macos-bar/VERSION`. Ad-hoc signing is the default; use
|
||||
`CCS_BAR_SIGNING=developer-id` only with the configured Apple signing and
|
||||
notarization credentials.
|
||||
@@ -57,36 +57,9 @@ if git show-ref --verify --quiet "refs/remotes/origin/$BASE_BRANCH"; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Hardening inventory freshness: the maintainability metrics artifact must be
|
||||
# regenerated within 30 days so the burndown stays current. Runs only after the
|
||||
# skip conditions above (CCS_SKIP_PREPUSH_GATE, detached HEAD, behind origin).
|
||||
HARDENING_JSON="docs/reports/hardening-inventory.json"
|
||||
if [[ ! -f "$HARDENING_JSON" ]]; then
|
||||
echo "[X] Missing $HARDENING_JSON."
|
||||
echo " Regenerate with: bun run report:hardening"
|
||||
exit 1
|
||||
fi
|
||||
HARDENING_TS=""
|
||||
# If the working-tree copy differs from HEAD (contributor regenerated but not
|
||||
# yet committed), use filesystem mtime; otherwise use the last commit time,
|
||||
# which is stable across CI clones (checkout resets mtimes) and so correctly
|
||||
# flags a stale committed artifact.
|
||||
if git diff --quiet -- "$HARDENING_JSON" 2>/dev/null && git diff --cached --quiet -- "$HARDENING_JSON" 2>/dev/null; then
|
||||
HARDENING_TS=$(git log -1 --format=%ct -- "$HARDENING_JSON" 2>/dev/null)
|
||||
else
|
||||
HARDENING_TS=$(stat -f %m "$HARDENING_JSON" 2>/dev/null || stat -c %Y "$HARDENING_JSON" 2>/dev/null)
|
||||
fi
|
||||
if [[ -n "$HARDENING_TS" ]]; then
|
||||
NOW_TS=$(date +%s)
|
||||
AGE_DAYS=$(( (NOW_TS - HARDENING_TS) / 86400 ))
|
||||
if (( AGE_DAYS > 30 )); then
|
||||
echo "[X] Hardening inventory is stale (${AGE_DAYS}d old; max 30d)."
|
||||
echo " Regenerate with: bun run report:hardening"
|
||||
echo " Then commit docs/reports/hardening-inventory.{json,md}."
|
||||
exit 1
|
||||
fi
|
||||
echo "[i] Hardening inventory fresh (${AGE_DAYS}d old; max 30d)."
|
||||
fi
|
||||
# Age does not prove that generated metrics match the checked-out source tree.
|
||||
# Compare both inventory artifacts byte-for-byte before expensive parity checks.
|
||||
node scripts/hardening-inventory.js --check
|
||||
|
||||
echo "[i] Running CI-parity local checks..."
|
||||
# `set -euo pipefail` above makes every step fail fast. Keep these commands
|
||||
|
||||
@@ -4,6 +4,7 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const { collectMaintainabilityMetrics } = require('./maintainability-metrics.js');
|
||||
const { isTestFile } = require('./runtime-source-classifier.js');
|
||||
|
||||
const ROOT_DIR = path.resolve(__dirname, '..');
|
||||
const SRC_DIR = path.join(ROOT_DIR, 'src');
|
||||
@@ -98,7 +99,7 @@ function isSourceFile(filePath) {
|
||||
}
|
||||
|
||||
function isHotpath(filePath) {
|
||||
return HOTPATH_PATTERNS.some((pattern) => pattern.test(filePath));
|
||||
return !isTestFile(filePath) && HOTPATH_PATTERNS.some((pattern) => pattern.test(filePath));
|
||||
}
|
||||
|
||||
function walkFiles(dirPath) {
|
||||
@@ -450,7 +451,9 @@ function renderMarkdown(report) {
|
||||
lines.push(`| Sync fs occurrences (all) | ${report.syncFs.totalOccurrences} |`);
|
||||
lines.push(`| Sync fs files affected (all) | ${report.syncFs.filesAffected} |`);
|
||||
lines.push(`| Sync fs occurrences (runtime hotpaths) | ${report.syncFs.hotpathOccurrences} |`);
|
||||
lines.push(`| Sync fs files affected (runtime hotpaths) | ${report.syncFs.hotpathFilesAffected} |`);
|
||||
lines.push(
|
||||
`| Sync fs files affected (runtime hotpaths) | ${report.syncFs.hotpathFilesAffected} |`
|
||||
);
|
||||
lines.push(`| Legacy shim markers | ${report.legacyShim.totalMarkers} |`);
|
||||
lines.push(`| Legacy shim files affected | ${report.legacyShim.filesAffected} |`);
|
||||
lines.push('');
|
||||
@@ -547,10 +550,39 @@ function renderMarkdown(report) {
|
||||
|
||||
function main() {
|
||||
const report = buildReport();
|
||||
const jsonContent = JSON.stringify(report, null, 2) + '\n';
|
||||
const markdownContent = renderMarkdown(report);
|
||||
const checkOnly = process.argv.includes('--check');
|
||||
|
||||
if (checkOnly) {
|
||||
const staleReports = [
|
||||
[JSON_REPORT_PATH, jsonContent],
|
||||
[MD_REPORT_PATH, markdownContent],
|
||||
].filter(([reportPath, expected]) => {
|
||||
try {
|
||||
return fs.readFileSync(reportPath, 'utf8') !== expected;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (staleReports.length > 0) {
|
||||
console.error('[X] Hardening inventory does not match the current source tree:');
|
||||
for (const [reportPath] of staleReports) {
|
||||
console.error(` ${relativePath(reportPath)}`);
|
||||
}
|
||||
console.error(' Regenerate with: bun run report:hardening');
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[OK] Hardening inventory matches the current source tree.');
|
||||
return;
|
||||
}
|
||||
|
||||
fs.mkdirSync(REPORT_DIR, { recursive: true });
|
||||
fs.writeFileSync(JSON_REPORT_PATH, JSON.stringify(report, null, 2) + '\n', 'utf8');
|
||||
fs.writeFileSync(MD_REPORT_PATH, renderMarkdown(report), 'utf8');
|
||||
fs.writeFileSync(JSON_REPORT_PATH, jsonContent, 'utf8');
|
||||
fs.writeFileSync(MD_REPORT_PATH, markdownContent, 'utf8');
|
||||
|
||||
const relJson = relativePath(JSON_REPORT_PATH);
|
||||
const relMd = relativePath(MD_REPORT_PATH);
|
||||
@@ -575,6 +607,7 @@ function main() {
|
||||
module.exports = {
|
||||
buildReport,
|
||||
collectSyncCallSites,
|
||||
isTestFile,
|
||||
renderMarkdown,
|
||||
stripComments,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { isTestFile } = require('./runtime-source-classifier.js');
|
||||
|
||||
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
|
||||
|
||||
@@ -79,14 +80,6 @@ function isSourceFile(filePath) {
|
||||
return SOURCE_EXTENSIONS.has(path.extname(filePath));
|
||||
}
|
||||
|
||||
function isTestFile(relPath) {
|
||||
return (
|
||||
/(?:^|\/)(?:__tests__|tests?)\//.test(relPath) ||
|
||||
/\.test\./.test(relPath) ||
|
||||
/\.spec\./.test(relPath)
|
||||
);
|
||||
}
|
||||
|
||||
function isCliUxExempt(relPath) {
|
||||
return CLI_UX_EXEMPT_PREFIXES.some(function (prefix) {
|
||||
return relPath.startsWith(prefix);
|
||||
@@ -350,6 +343,7 @@ module.exports = {
|
||||
classifyThrows: classifyThrows,
|
||||
countConsoleErrors: countConsoleErrors,
|
||||
hasCreateLogger: hasCreateLogger,
|
||||
isTestFile: isTestFile,
|
||||
countLoc: countLoc,
|
||||
TYPED_ERROR_CLASSES: TYPED_ERROR_CLASSES,
|
||||
TYPED_ADOPTION_SUBDOMAINS: TYPED_ADOPTION_SUBDOMAINS,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
function isTestFile(filePath) {
|
||||
const normalizedPath = filePath.replace(/\\/g, '/');
|
||||
return (
|
||||
/(^|\/)(__tests__|__mocks__|fixtures)(\/|$)/.test(normalizedPath) ||
|
||||
/(^|\/)(tests?|test-fixtures)(\/|$)/.test(normalizedPath) ||
|
||||
/\.(test|spec)\.[^.]+$/.test(normalizedPath)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isTestFile,
|
||||
};
|
||||
@@ -17,13 +17,13 @@ import {
|
||||
import { ExitCode } from '../exit-codes';
|
||||
|
||||
/**
|
||||
* P4 behavior-lock: the typed-error -> exit-code mapping is the contract this
|
||||
* epic relies on. Migrating `throw new Error` to typed subclasses changes the
|
||||
* process exit code (via handleError -> getExitCode); these tests lock the
|
||||
* mapping so a future change is caught. See
|
||||
* The typed-error -> exit-code mapping is a runtime compatibility contract.
|
||||
* Migrating `throw new Error` to typed subclasses changes the process exit code
|
||||
* through handleError; these tests lock the mapping so a future change is
|
||||
* caught. See
|
||||
* docs/reports/typed-error-exit-code-compat-audit.md.
|
||||
*/
|
||||
describe('typed-error taxonomy -> exit-code mapping (P4 contract)', () => {
|
||||
describe('typed-error taxonomy -> exit-code compatibility contract', () => {
|
||||
test('each typed class carries its documented ExitCode', () => {
|
||||
expect(new ConfigError('m').code).toBe(ExitCode.CONFIG_ERROR);
|
||||
expect(new NetworkError('m').code).toBe(ExitCode.NETWORK_ERROR);
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
const failures = [];
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(root, relativePath), 'utf8');
|
||||
}
|
||||
|
||||
function requireText(relativePath, expected) {
|
||||
if (!read(relativePath).includes(expected)) {
|
||||
failures.push(`${relativePath} is missing: ${expected}`);
|
||||
}
|
||||
}
|
||||
|
||||
function collectFiles(directory, filePattern) {
|
||||
const files = [];
|
||||
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
||||
const absolutePath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
files.push(...collectFiles(absolutePath, filePattern));
|
||||
} else if (entry.isFile() && filePattern.test(entry.name)) {
|
||||
files.push(absolutePath);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
const removedGuides = [
|
||||
'docs/ccs-bar.md',
|
||||
'docs/cursor-integration.md',
|
||||
'docs/dashboard-auth-cli.md',
|
||||
'docs/session-sharing-technical-analysis.md',
|
||||
];
|
||||
|
||||
for (const relativePath of removedGuides) {
|
||||
if (fs.existsSync(path.join(root, relativePath))) {
|
||||
failures.push(`${relativePath} should use its canonical public-doc replacement`);
|
||||
}
|
||||
}
|
||||
|
||||
requireText('README.md', 'https://docs.ccs.kaitran.ca/features/proxy/openai-compatible-providers');
|
||||
requireText('README.md', 'https://docs.ccs.kaitran.ca/features/workflow/browser-automation');
|
||||
requireText('docs/browser-automation.md', 'CCS_BROWSER_INTERCEPT_FULFILL_MODE=enabled');
|
||||
requireText('docs/browser-automation.md', 'CCS_BROWSER_UPLOAD_ROOTS');
|
||||
requireText('docs/browser-automation.md', 'CCS_BROWSER_DOWNLOAD_ROOTS');
|
||||
requireText('docs/browser-automation.md', 'browser_wait_for_event');
|
||||
requireText('docs/browser-automation.md', 'path-scoped bearer values');
|
||||
requireText('docs/codex-auth.md', 'src/codex-auth/codex-auth-help.ts');
|
||||
requireText('docs/codex-auth.md', 'CCSXP_CODEX_HOME');
|
||||
requireText('docs/image-analysis.md', 'https://docs.ccs.kaitran.ca/features/ai/image-analysis');
|
||||
requireText('docs/openai-compatible-providers.md', 'CCS_OPENAI_PROXY_INSECURE');
|
||||
requireText('docs/openai-compatible-providers.md', 'CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS');
|
||||
requireText('macos-bar/README.md', 'macos-bar/VERSION');
|
||||
requireText('macos-bar/README.md', '.github/workflows/bar-release.yml');
|
||||
requireText(
|
||||
'.github/ISSUE_TEMPLATE/documentation.yml',
|
||||
'https://docs.ccs.kaitran.ca/providers/oauth/cursor'
|
||||
);
|
||||
|
||||
const practicalGuidanceFiles = [
|
||||
path.join(root, 'README.md'),
|
||||
path.join(root, 'CLAUDE.md'),
|
||||
path.join(root, 'CONTRIBUTING.md'),
|
||||
path.join(root, 'SECURITY.md'),
|
||||
path.join(root, 'docker', 'README.md'),
|
||||
path.join(root, 'macos-bar', 'README.md'),
|
||||
...collectFiles(path.join(root, 'docs'), /\.mdx?$/),
|
||||
...collectFiles(path.join(root, '.github', 'ISSUE_TEMPLATE'), /\.(md|ya?ml)$/),
|
||||
];
|
||||
|
||||
for (const staleGuidePath of removedGuides) {
|
||||
for (const guidancePath of practicalGuidanceFiles) {
|
||||
if (read(path.relative(root, guidancePath)).includes(staleGuidePath)) {
|
||||
failures.push(
|
||||
`${path.relative(root, guidancePath)} references deleted guide: ${staleGuidePath}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const markdownFiles = [
|
||||
path.join(root, 'README.md'),
|
||||
path.join(root, 'docker', 'README.md'),
|
||||
path.join(root, 'macos-bar', 'README.md'),
|
||||
...collectFiles(path.join(root, 'docs'), /\.mdx?$/),
|
||||
];
|
||||
const linkPattern = /!?\[[^\]]*]\(([^)]+)\)/g;
|
||||
|
||||
for (const markdownPath of markdownFiles) {
|
||||
const source = fs.readFileSync(markdownPath, 'utf8');
|
||||
for (const match of source.matchAll(linkPattern)) {
|
||||
let target = match[1].trim().replace(/^<|>$/g, '');
|
||||
if (!target || target.startsWith('#') || /^(https?:|mailto:|tel:)/i.test(target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
target = target.split('#', 1)[0].split('?', 1)[0];
|
||||
try {
|
||||
target = decodeURIComponent(target);
|
||||
} catch {
|
||||
failures.push(`${path.relative(root, markdownPath)} has invalid link encoding: ${match[1]}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolved = path.resolve(path.dirname(markdownPath), target);
|
||||
if (!fs.existsSync(resolved)) {
|
||||
failures.push(`${path.relative(root, markdownPath)} has missing relative link: ${match[1]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error('[X] Documentation freshness checks failed:');
|
||||
for (const failure of failures) {
|
||||
console.error(` ${failure}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('[OK] Documentation pointers, retained contracts, and relative links are current.');
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
|
||||
const { collectSyncCallSites } = require('../../../scripts/hardening-inventory.js');
|
||||
const { collectSyncCallSites, isTestFile } = require('../../../scripts/hardening-inventory.js');
|
||||
|
||||
describe('hardening-inventory sync call scanning', () => {
|
||||
test('ignores sync-call names inside regex literals after else', () => {
|
||||
@@ -34,3 +34,22 @@ describe('hardening-inventory sync call scanning', () => {
|
||||
expect(result.calls).toEqual(['readFileSync']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('hardening-inventory runtime file classification', () => {
|
||||
test.each([
|
||||
'src/cliproxy/__tests__/routing.test.ts',
|
||||
'src/commands/fixtures/help-output.ts',
|
||||
'src/auth/profile.spec.ts',
|
||||
'tests/unit/commands/profile.test.ts',
|
||||
])('excludes %s from runtime hotpaths', (filePath) => {
|
||||
expect(isTestFile(filePath)).toBe(true);
|
||||
});
|
||||
|
||||
test.each([
|
||||
'src/cliproxy/routing/retry-settings.ts',
|
||||
'src/commands/help-command.ts',
|
||||
'src/utils/browser/mcp-installer.ts',
|
||||
])('keeps %s eligible for runtime hotpaths', (filePath) => {
|
||||
expect(isTestFile(filePath)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -8,10 +8,31 @@ const {
|
||||
classifyThrows,
|
||||
countConsoleErrors,
|
||||
hasCreateLogger,
|
||||
isTestFile,
|
||||
countLoc,
|
||||
collectMaintainabilityMetrics,
|
||||
} = require('../../../scripts/maintainability-metrics.js');
|
||||
|
||||
describe('maintainability-metrics runtime file classification', () => {
|
||||
test.each([
|
||||
'src/cliproxy/__tests__/routing.test.ts',
|
||||
'src/commands/fixtures/help-output.ts',
|
||||
'src/auth/profile.spec.ts',
|
||||
'tests/unit/commands/profile.test.ts',
|
||||
'src\\auth\\__mocks__\\profile.ts',
|
||||
])('excludes %s from runtime metrics', (filePath) => {
|
||||
expect(isTestFile(filePath)).toBe(true);
|
||||
});
|
||||
|
||||
test.each([
|
||||
'src/cliproxy/routing/retry-settings.ts',
|
||||
'src/commands/help-command.ts',
|
||||
'src/utils/browser/mcp-installer.ts',
|
||||
])('keeps %s in runtime metrics', (filePath) => {
|
||||
expect(isTestFile(filePath)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('maintainability-metrics.classifyThrows', () => {
|
||||
test('counts plain Error, typed, and other throws; ignores re-throws', () => {
|
||||
const source = [
|
||||
@@ -94,14 +115,18 @@ describe('maintainability-metrics.collectMaintainabilityMetrics (fixtures tree)'
|
||||
fs.mkdirSync(path.join(root, 'src', 'commands'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(root, 'src', 'commands', 'b.ts'),
|
||||
['console.error(\'cli print\');', "throw new Error('plain');"].join('\n')
|
||||
["console.error('cli print');", "throw new Error('plain');"].join('\n')
|
||||
);
|
||||
|
||||
// src/cliproxy/quota/q.ts: createLogger present, 1 plain throw, 0 console.error
|
||||
fs.mkdirSync(path.join(root, 'src', 'cliproxy', 'quota'), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(root, 'src', 'cliproxy', 'quota', 'q.ts'),
|
||||
["import { createLogger } from '../../../services/logging';", 'const logger = createLogger();', "throw new Error('quota plain');"].join('\n')
|
||||
[
|
||||
"import { createLogger } from '../../../services/logging';",
|
||||
'const logger = createLogger();',
|
||||
"throw new Error('quota plain');",
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
// non-source file is ignored
|
||||
|
||||
Reference in new issue
Block a user