Commit Graph
3951 Commits
Author SHA1 Message Date
Kai (Tam Nhu) TranandGitHub 1f736b2da9 fix(ci): smoke-test failure check + relax service-key guard (REV9, REV10) (#1276)
REV9 — docker-release.yml smoke-test: add HEALTHY flag to the boot-and-wait
loop. Previously a timeout (status stays 'starting'/'missing' for all 12
iterations) exited the loop silently, letting port probes be the only net.
Now if the loop exits without HEALTHY=1 the step fails immediately with
container logs and state dump. network-contract.sh already has the HEALTHY
pattern — no change needed there.

REV10 — breaking-change-guard.yml: drop the OLD_KEYS != NEW_KEYS comparison.
That check treated ANY change to the service-key set as breaking — including
adding a harmless sidecar. The DNS contract only requires services.ccs to
exist; other services are irrelevant to the 'ccs' hostname on ccs-net. Keep
only the "ccs key must exist" check; remove the unused OLD_KEYS extraction.
2026-05-17 05:26:42 -04:00
Kai (Tam Nhu) TranandGitHub a4e16e0b09 fix: tighten parity check + catch service-key rename (#1261 loop 3) (#1274)
* fix(test): tighten compose-parity image-name match to exact expected values (REV7)

Replace loose substring grep (grep -q "ccs") with exact equality checks
against declared EXPECTED_CANONICAL_IMAGE and EXPECTED_INTEGRATED_IMAGE
constants. The integrated compose builds locally as ccs-cliproxy:latest
(not ghcr.io/kaitranntt/ccs), so both expected names are explicitly
documented at the top of the assertion block.

Fixes: a drift in integrated compose to a wrong owner/registry could
slip through the old "grep -q ccs" check; now any name other than the
declared constant is a hard failure.

* feat(ci): breaking-change-guard catches services.ccs rename — public DNS contract (REV8)

Docker's service-name DNS uses the compose service KEY as the hostname.
Sibling containers on ccs-net reach CCS via http://ccs:8317; renaming
services.ccs: to anything else silently breaks that contract even when
image name, network name, and container_name are unchanged.

Add check 4 to the guard:
- Extract top-level service keys from both base and HEAD versions of
  docker/compose.yaml using awk (no external YAML parser required).
- Fail if the "ccs" key is absent from HEAD.
- Fail if the sorted set of service keys differs from base.

Wraps inside the existing git cat-file guard so new-file PRs skip it.
2026-05-17 05:12:04 -04:00
Kai (Tam Nhu) TranandGitHub adf5a836fc fix: address reviewer findings round 2 (#1261 loop 2) (#1272)
* fix(docker): parameterize image with CCS_IMAGE env so smoke-test exercises new digest

docker/compose.yaml hardcoded image: ghcr.io/kaitranntt/ccs:latest, causing
network-contract.sh to always use the old published image regardless of what
IMAGE_OVERRIDE was passed. Switching to \${CCS_IMAGE:-ghcr.io/kaitranntt/ccs:latest}
lets CI pass CCS_IMAGE=<digest> so the smoke-test actually exercises the
just-built image. Default end-user behaviour is unchanged.

Resolves REV4 (reviewer loop 2, PR #1261).

* fix(test): image-size.sh fails loudly on manifest inspection failure

The --platform branch previously exited 0 when imagetools inspect returned
empty or zero bytes, silently passing the budget check. A broken image
could reach production undetected if buildx had any inspection issue.

Changed to exit 1 with a clear diagnostic message explaining possible causes
(old buildx, manifest format mismatch, image not yet pushed). No --allow-inspect-failure
escape hatch is provided; CI must fix the root cause.

Resolves REV5 (reviewer loop 2, PR #1261).

* test(docker): cover --platform branch in image-size-logic tests

Prior tests only exercised the local docker image inspect path. The
--platform branch (imagetools inspect) had zero coverage, meaning the
REV5 silent-pass regression would not have been caught by CI.

Added 5 mock-based test cases for the --platform branch:
- pass when platform-scoped compressed size < budget
- fail when platform-scoped compressed size > budget
- fail (exit 1) when imagetools inspect errors (REV5 guard)
- fail (exit 1) when reported size is "0" (REV5 guard)
- fail (exit 1) when size output is empty (REV5 guard)

Mocks follow the existing pattern (override docker in PATH with a temp
wrapper), extended to handle buildx imagetools inspect subcommand.

Resolves REV6 (reviewer loop 2, PR #1261).

* fix(test): compose-parity image_name() handles \${VAR:-default} syntax

After parameterizing compose.yaml's image field with \${CCS_IMAGE:-...},
the image_name() sed pipeline cut at the first colon in the shell variable
syntax (:-) instead of the tag separator, returning '\${CCS_IMAGE' and
failing the kaitranntt/ccs grep.

Added a sed step to unwrap \${VAR:-default} by extracting just the default
value before stripping the tag. Existing plain image: name:tag references
are unaffected.
2026-05-17 05:01:15 -04:00
Kai (Tam Nhu) TranandGitHub 78004746be fix: address upstream reviewer findings + failing CI checks (#1261 loop 1) (#1271)
* fix(ci): breaking-change-guard skips missing base files (CI-1)

Guard each git-show call with git cat-file -e existence check before
attempting to read docker/compose.yaml from the base branch. When the
file doesn't exist on the base (new file in this PR), the script would
crash with "fatal: path exists on disk but not in origin/dev". New
files can't cause a contract regression, so we exit early with
breaking=0.

* style: prettier reformat unrelated drift (CI-2)

Two files had minor formatting drift from earlier umbrella PRs:
- src/cliproxy/quota/quota-manager.ts
- src/management/checks/image-analysis-check.ts

No logic changes — formatter-only pass to unblock CI format:check.

* fix(test): update trusted-author gate count to 4 in ci-workflow test (CI-3)

PR #1260 added a compose-parity job to ci.yml. That job runs on
self-hosted runners using PR-provided checkout, so it legitimately
requires the trusted-author guard (same as validate, build, test).

The test expected 3 occurrences; the correct count is now 4:
  validate (matrix), build, test, compose-parity.

* fix(ci): smoke-test passes compose path + image ref to network-contract (REV-1)

network-contract.sh signature is: <compose-file> [image-ref]
The smoke-test job was calling it as:
  bash tests/docker/network-contract.sh "${{ steps.image.outputs.ref }}"
which placed the image ref in the compose-file position ($1), causing
the script to try docker compose -f <image-ref> which fails.

Corrected to:
  bash tests/docker/network-contract.sh docker/compose.yaml "${{ steps.image.outputs.ref }}"

Also removes publish-dashboard from smoke-test.needs (REV-2): when
publish-dashboard is SKIPPED on prerelease events, GitHub Actions
propagates the skip to downstream jobs, so smoke-test and
promote-mutable-tags were silently skipped on every rc.N publish.
smoke-test only verifies the integrated image; it has no dependency
on the legacy dashboard image job.

* docs(docker): annotate /root/.ccs path in compose volume (REV-3 clarification)

The reviewer raised a concern that the compose volume mounts /root/.ccs
but the entrypoint might default to /home/node/.ccs. This is a false
positive: the integrated image uses entrypoint-integrated.sh (not
entrypoint.sh), which runs under supervisord with user=root and
explicitly mkdir -p /root/.ccs. HOME is /root inside the container.
The volume mount at /root/.ccs is correct.

Added an inline comment documenting the reasoning so future reviewers
do not confuse entrypoint.sh (legacy dashboard image) with
entrypoint-integrated.sh (integrated image).

* fix(ci): gate docs-parity pull_request job to trusted authors

docs-parity.yml runs on a self-hosted runner and checks out PR code.
The self-hosted-runner-policy test requires any such workflow to include
the trusted-author guard. The workflow was missing the guard, causing
bun test:fast to fail with 1 failure.

Allow push events (no author check needed — push is to own branch)
and trusted-contributor PRs only.
2026-05-16 13:57:50 -04:00
Kai (Tam Nhu) TranandGitHub 107b5b5db4 fix(docker): apply red-team findings — drop :full, rc.1 soak, healthcheck, signing (#1251) (#1262)
* docs(quickstart): fix raw URL for corporate-proxy fallback (H1)

* feat(docker)!: drop :full image variant — use sibling containers on ccs-net (Q3)

No AI CLIs (claude-code/gemini-cli/grok-cli/opencode) are bundled in the image.
Use sibling containers attached to ccs-net instead.
See docker/README.md#connect-your-app-to-cliproxy.

Also removes bash from apk deps (entrypoint uses #!/bin/sh — L2).

ci(docker): publish only immutable :<ver> tag pre-smoke; promote-mutable-tags
job adds :latest/:MAJOR/:MINOR aliases only after smoke tests pass (H3)

ci(docker): smoke-test-compose-url runs network-contract.sh against the
downloaded /tmp/ccs-compose.yaml instead of re-cloning the repo (H4)

ci(docker): sign published images with cosign keyless OIDC + attach
provenance/SBOM via build-push-action (M8)

test(docker): network-contract.sh now accepts compose-file and image-ref
positional args; replaces python3 healthcheck parser with jq (L4)

* chore(release): cut every main release as rc.N prerelease, manual promote flow (H2)

- .releaserc.cjs: main branch now uses prerelease 'rc' channel — every
  semantic-release cut becomes vX.Y.Z-rc.N
- add promote-release.yml: workflow_dispatch flips rc → stable via
  'gh release edit --prerelease=false'; triggers docker promote-mutable-tags
- add docs/release-process.md: full soak + promote procedure, rollback steps,
  cosign verification command
- releaseNotesGenerator: add revert section, document chore hidden behaviour (L8)

* fix(docker): healthcheck probes both dashboard and cliproxy ports (M1)

compose.yaml healthcheck now checks :3000 and :8317 concurrently with
a 4.5s internal timeout, within Docker's 5s timeout budget.

Also:
- docs(docker): document npm lockfile ephemeral tradeoff above install
  layer; note size-budget regression test as the practical safeguard (M3)
- docs(docker): drop :full row from Choosing an image table; add sibling
  container note pointing to connect-your-app-to-cliproxy (Q3/docs)
- docs(docker): remove :full docker run block; fix release-tag sentence (Q3)
- docs(docker): fix raw URL in migration section (H1 parity)
- docs(docker): add Volume warning — 'down -v' deletes named volumes (L13)
- docs(docker): update What changes table — remove :full reference (Q3)
- docs(docker): add Image Signatures and SBOM section with cosign verify
  and sbom download commands (M8/docs)
- changelog: add Unreleased entries for rc soak, cosign signing, :full
  removal with migration guidance

* ci(docker): assert image-size budget per platform; add compose parity + breaking-change guard (M6/L9/L12)

- image-size.sh: add --platform flag; uses 'docker buildx imagetools
  inspect' to sum compressed layer sizes from registry manifest for
  linux/amd64 and linux/arm64 separately (M6)
- docker-release.yml smoke-test: runs size check for both platforms
- compose-parity.sh: diffs docker/compose.yaml vs
  docker/docker-compose.integrated.yml for image name, ports 3000/8317,
  volume mounts /root/.ccs and /var/log/ccs, ccs-net definition (L12)
- ci.yml: add compose-parity job wired to cliproxy runner (L12)
- breaking-change-guard.yml: fails PR if compose.yaml changes image name,
  network name, or container_name without a feat!/fix! commit (L9)

* chore(ci): fix cosign shell substitution — use tr instead of bash @L expansion (L6/nit)

* test(docker): fix compose-parity port regex for variable-interpolated host ports
2026-05-16 13:33:12 -04:00
Kai (Tam Nhu) TranandGitHub b50c2db3ce docs(docker): P3 — hoist two-command quickstart, restructure docker/README, add parity CI (#1260)
* docs: hoist Docker zero-install quickstart above npm install path

- Create docs/quickstart-snippet.md as canonical source for the
  two-command flow (curl + docker compose up -d), wrapped in
  <!-- quickstart-snippet-start/end --> markers
- Hoist the snippet into README.md immediately below the deprecation
  banner, above all other install paths
- Rename old npm-only "## Quick Start" to "## Install on Host (npm)"
  and move it below the Docker quickstart

* docs(docker): restructure README with zero-install first and migration section

- Reorder top-level sections: zero-install (canonical snippet with
  markers), choosing an image, power-user ccs docker, prebuilt image,
  connect your app to CLIProxy, migration, env vars, troubleshooting
- Add deprecation banner at the top pointing at the migration section
- Add ## Migration from ccs-dashboard:latest section with step-by-step
  instructions covering compose down, data preservation, named volume
  vs bind-mount path, and compose up with the new image
- Keep P1's Choosing an image table and P5's Connect Your App to
  CLIProxy section intact, just repositioned

* test(docs): parity check for quickstart snippet across README files

Assert README.md and docker/README.md both contain the canonical
quickstart block verbatim, anchored by marker comments. Exits non-zero
and prints a diff on any drift.

* ci(docs): wire quickstart-parity test on push and PR

Runs tests/docs/quickstart-parity.sh on self-hosted runner whenever
docs/quickstart-snippet.md, README.md, docker/README.md, or the
test/workflow files themselves change. Fails fast on snippet drift.
2026-05-16 12:56:09 -04:00
Kai (Tam Nhu) TranandGitHub 28f08cbb50 docs(docker): document ccs-net contract for sibling containers (#1259)
Add "Connect your app to CLIProxy" section to docker/README.md with:
- Public contract table (network=ccs-net, service DNS=ccs, ports 8317/3000)
- Pattern A: same compose file with external network reference
- Pattern B: docker run --network ccs-net
- Troubleshooting subsection (DNS, missing network, conflict, Podman, MTU)

Add one-line link in README.md pointing to the new section.
Add CHANGELOG entry under Unreleased noting the contract as SemVer-major stable.
Add CONTRIBUTING.md note that changing services.ccs or networks.ccs-net requires major bump.
2026-05-16 12:46:33 -04:00
Kai (Tam Nhu) TranandGitHub fbaac6a9ba feat(docker): canonical compose.yaml + smoke-test (P2 of #1251) (#1258)
* feat(docker): add canonical compose.yaml for zero-install flow (#1251)

* ci(docker): smoke-test ccs.kaitran.ca/docker-compose.yaml on release + nightly (#1251)
2026-05-16 12:39:50 -04:00
Kai (Tam Nhu) TranandGitHub 775f438d78 refactor(docker): drop Bun from runtime stage; generate npm lockfile in build stage (#1256)
- Build stage keeps Bun for fast installs; appends `npm install --package-lock-only`
  to generate an ephemeral package-lock.json immediately after `bun run build:all`.
- Runtime stage removes BUN_VERSION ARG/ENV, the bun.sh curl install, and
  `bun install --frozen-lockfile --production`. Replaces with:
    COPY --from=build /app/package.json /app/package-lock.json ./
    RUN npm ci --omit=dev --ignore-scripts
- --ignore-scripts rationale: postinstall (scripts/postinstall.js) writes ~/.ccs/
  config — not needed in Docker context. bcrypt v6+ is pure-JS, no native compile.
- package-lock.json already in .gitignore (line 33); never committed.
- docker/Dockerfile.integrated unchanged — no Bun present there (alpine + npm).
- Targets: image size reduction >= 300 MB by eliminating ~130 MB Bun binary + installer.
2026-05-16 12:33:45 -04:00
Kai (Tam Nhu) TranandGitHub d558cd2e36 feat(docker): publish ccs:latest + ccs:full integrated images (P1 of #1251) (#1257)
* feat(docker): parameterize Dockerfile.integrated with ARG FLAVOR=minimal|full

Add FLAVOR build arg that gates the AI CLI install layer (claude-code,
gemini-cli, grok-cli, opencode) so one Dockerfile produces both the
minimal (< 350 MB) and full (< 600 MB) integrated images.

Use BuildKit cache mount for /root/.npm to speed up repeated builds.
Part of #1251 (P1 — publish integrated images).

* feat(docker): emit startup deprecation warning in legacy ccs-dashboard entrypoint

Prepend a [WARN] line to stderr on every container start so operators
running ghcr.io/kaitranntt/ccs-dashboard:latest are notified to migrate
to ghcr.io/kaitranntt/ccs:latest. Sunset window: 2 releases. See #1251.

* test(docker): add image-size.sh budget assertion + unit test suite

image-size.sh: asserts docker image inspect .Size against a byte budget.
  Usage: image-size.sh <image:tag> <max-bytes>
  Budgets: minimal=350 MB (367001600), full=600 MB (629145600)

image-size-logic.test.sh: 6 mock-docker unit tests covering pass/fail
boundaries, bad arg count, and non-integer input. All 6 pass locally.
Part of #1251 (P1).

* ci(docker): extend docker-release.yml to publish ccs:latest and ccs:full

- Add publish-integrated job with matrix flavor: [minimal, full]
  - minimal -> ghcr.io/kaitranntt/ccs:<ver> + :latest (when promoted)
  - full    -> ghcr.io/kaitranntt/ccs:full-<ver> + :full (when promoted)
  - Multi-arch: linux/amd64 + linux/arm64 via buildx
  - Scoped GHA cache per flavor to avoid cross-contamination

- Add promote_to_latest workflow_dispatch input (default false)
  - rc.1 soak: first publish only pushes immutable version tag
  - Mutable :latest/:full promoted only on release event OR explicit opt-in

- Add smoke-test job (post-publish) for each flavor
  - Pulls the just-published version tag
  - Asserts image size via tests/docker/image-size.sh
  - Boots container, waits for healthcheck, probes :3000 and :8317

- Keep publish-dashboard job unchanged (legacy 2-release sunset)
  - Updated labels to note deprecation status

All jobs run on self-hosted cliproxy runners. Part of #1251 (P1).

* docs(docker): document new image tags, add ccs-dashboard deprecation notices

docker/README.md:
- Add "Choosing an image" table (ccs:latest / ccs:full / ccs-dashboard deprecated)
- Update Quick Start section to use ccs:latest as primary example
- Add legacy image note with sunset timeline

CHANGELOG.md:
- Add Unreleased > ### Deprecated entry for ccs-dashboard:latest
  pointing to migration path and #1251

README.md:
- Add one-line deprecation banner near top routing users to ccs:latest

Part of #1251 (P1).
2026-05-16 12:33:42 -04:00
github-actions[bot] 423708f28f chore(release): 7.79.1-dev.3 2026-05-15 13:55:25 +00:00
Kai (Tam Nhu) TranandGitHub 609bed0c39 fix(codex): normalize native cliproxy tuning aliases (#1254) 2026-05-15 09:51:42 -04:00
github-actions[bot] 53730c0165 chore(release): 7.79.1-dev.2 2026-05-14 23:41:42 +00:00
Kai (Tam Nhu) TranandGitHub b0db476943 Merge pull request #1249 from kaitranntt/kai/feat/1241-korean-i18n
feat(dashboard): add Korean language support (#1241)
2026-05-14 19:38:07 -04:00
Tam Nhu Tran 8ad7653a2e feat(dashboard): add Korean language support
Adds Korean (ko) as a supported dashboard locale alongside en, zh-CN, vi, ja.
Browser locales starting with `ko` (e.g. `ko-KR`) normalize to `ko` and the
Language switcher exposes "Korean" / "한국어" as a selectable option.

The full ~2670-key translation block was translated from the English source
with the formal-polite register (하십시오체) appropriate for an enterprise
dashboard. Technical names (CCS, Claude, CLIProxy, OAuth, provider names,
env vars, paths) are kept untranslated. All placeholders (e.g. {{name}},
{{count}}, {{provider}}) are preserved verbatim, matching the parity test.

Also backfills 23 keys missing from zh-CN, vi, and ja that were added to en
with the recent "Shared Resource Controls" feature (#1206 -> 2a422b3b):
accountsPage.resources*, accountsTable.{sharedResources,resourcesTitle,
resourcesShared,resourcesProfileLocal,badges.profileLocal},
commonToast.resourcesUpdated, and the full editAccountSharedResources
section. Without this backfill the existing i18n parity test was red for
all non-en locales on dev.

Closes #1241
2026-05-14 19:14:13 -04:00
github-actions[bot] 0213aac178 chore(release): 7.79.1-dev.1 2026-05-14 22:27:40 +00:00
Kai (Tam Nhu) TranandGitHub 1112fd0465 Merge pull request #1248 from kaitranntt/kai/fix/codex-config-toml-selection
fix(ui): make codex config.toml viewer selectable
2026-05-14 18:23:53 -04:00
Tam Nhu Tran 15a1faf3ab fix(ui): handle TOML triple-quotes in arrays and AST-aware JSON keys
PR-Agent on #1248 flagged two remaining sensitive-mask edge cases:

1. The TOML array walker stepped through triple-quoted strings one
   single quote at a time. With a body containing both quotes and
   structural characters, like """abc"]xyz""", a stray `]` inside the
   string body could escape the string-skip pair and terminate the
   array scan early. Add skipTomlString that recognizes triple-quoted
   strings ("""…""", '''…''') and skips them as one unit.

2. The JSON regex matched any "...":  pattern, so substrings inside
   escaped value strings, like "description": "use \\"API_KEY\\": header",
   could mask unrelated text. Replace the JSON branch with a state-aware
   scanner that only registers a string as a key when it sits at an
   object-key position (preceded by `{` or `,`, followed by `:`).

Adds regression tests for both cases.
2026-05-14 18:10:26 -04:00
Tam Nhu Tran a633ee0af4 fix(ui): skip TOML comments when scanning sensitive array values
PR-Agent on #1248 flagged that the TOML array walker did not skip
`#` line comments while balancing brackets. A sensitive array with a
comment containing `]` could terminate the scan early and leave the
real tail of the secret unmasked. Skip from `#` to the next newline at
the array's top level. Adds a regression test.
2026-05-14 17:57:06 -04:00
Tam Nhu Tran f404e52439 perf(ui): tighten sensitive-mask decoration scope and reveal selector
PR-Agent review on #1248 flagged two follow-ups:

1. The reveal CSS used `.cm-editor.cm-sensitive-revealed`, but
   @uiw/react-codemirror puts the consumer className on its outer
   wrapper, not on .cm-editor. The selector never matched, so the Eye
   toggle could not un-blur masked values. Drop `.cm-editor` from the
   selector so it matches the wrapper that actually carries the class.

2. The sensitive-decoration plugin rebuilt on every `viewportChanged`,
   which means each scroll event re-stringified and rescanned the
   entire document even though decorations are doc-only-dependent.
   Rebuild only on `docChanged` to remove scroll-time work.
2026-05-14 17:50:50 -04:00
Tam Nhu Tran bac51ce1ff fix(ui): honor TOML quoted keys and escaped quotes in sensitive masking
Two remaining gaps in the sensitive-value detector flagged by PR-Agent
on #1248:

1. The TOML/YAML key regex only matched bare keys, so values written
   under quoted keys like `"API_KEY" = "secret"` were never masked.
   Accept basic-string and literal-string quoted keys and normalize the
   captured key (strip quotes, unescape) before isSensitiveKey.

2. The string-skip helper used indexOf for the next quote character, so
   a value like `AUTH_TOKEN = ["a \"quoted\" value", "x"]` could
   terminate scanning early and leak unrelated text or under-mask the
   secret. Introduce skipString that honors backslash escapes for basic
   strings and is used by both the TOML array walker and the JSON
   balanced-brace walker.

Adds regression tests for both edge cases.
2026-05-14 17:44:52 -04:00
Tam Nhu Tran eefb76214e fix(ui): mask multi-line sensitive values in CodeEditor
Extend the sensitive-key decoration to cover the full logical value, not
just the rest of the current line. Adds value-extent detection for:

- TOML triple-quoted strings (multi-line literal/basic strings)
- TOML arrays ([ ... ] across multiple lines, string-aware)
- YAML block scalars (| and > with indent-based termination)
- JSON arrays and objects (balanced braces, string-aware)

Without this, a secret value spanning multiple lines (multi-line API
key, certificate, private key) was only blurred on its opening line and
the rest stayed readable when the Eye toggle was masked.

Adds regression tests for the TOML triple-quote and array cases.
Flagged by PR-Agent review on #1248.
2026-05-14 17:38:06 -04:00
Tam Nhu Tran 0ce01d4c65 chore(ui): bump bundle size guard for CodeMirror editor upgrade
Total dist/ui gzipped grew from ~1.5MB to ~1.6MB after the CodeEditor
switch from react-simple-code-editor + prism-react-renderer to
CodeMirror 6. The verify-bundle script preamble notes the cap is a
sanity check for accidental large dependencies, not a hard
performance target; this is an intentional editor upgrade to restore
native selection/copy in the codex config.toml viewer.
2026-05-14 17:27:19 -04:00
Tam Nhu Tran 420494e0e0 fix(ui): make codex config.toml viewer selectable
Replace the hand-rolled textarea+Prism overlay in CodeEditor with
CodeMirror 6. The overlay hid the textarea selection with
`selection:bg-transparent` and `WebkitTextFillColor: transparent` as a
workaround for wrap drift between the highlight layer and the textarea,
which left Ctrl+A / drag-select / Ctrl+C with no visible result.

CodeMirror handles selection, copy, undo, and wrap natively. Custom
themes match the previous prism-react-renderer github (light) and
nightOwl (dark) palettes; background is transparent so the surrounding
bg-muted/30 still shows; font, font-size, line-height, padding, and
gutter-less layout are preserved.

Public CodeEditor props are unchanged (value, onChange, language,
readonly, exactText, className, minHeight, heightMode) so all seven
call sites keep working. The sensitive-key blur is reimplemented as a
CodeMirror Decoration and stays gated behind the existing Eye toggle.

Test setup gets a Range.getClientRects polyfill so CM mounts cleanly in
JSDOM, and shared-page.test.tsx checks .cm-content text directly.

Closes #1247
2026-05-14 17:22:26 -04:00
semantic-release-bot aba15d8646 chore(release): 7.79.1 [skip ci]
## [7.79.1](https://github.com/kaitranntt/ccs/compare/v7.79.0...v7.79.1) (2026-05-14)

### Hotfixes

* **codex:** tolerate BOM in Codex TOML config ([5b664d2](https://github.com/kaitranntt/ccs/commit/5b664d2105b81ec263c05a37c4549a4a6c409bb3))
2026-05-14 17:24:12 +00:00
Kai (Tam Nhu) TranandGitHub e1a34ce056 Merge pull request #1246 from kaitranntt/kai/hotfix-codex-toml-bom
hotfix(codex): tolerate BOM in Codex TOML config
2026-05-14 13:20:58 -04:00
Tam Nhu Tran 5b664d2105 hotfix(codex): tolerate BOM in Codex TOML config 2026-05-14 13:13:55 -04:00
semantic-release-bot 1773586d4d chore(release): 7.79.0 [skip ci]
## [7.79.0](https://github.com/kaitranntt/ccs/compare/v7.78.1...v7.79.0) (2026-05-14)

### Features

* **dispatcher:** pass through Claude subcommands without interactive-session args ([c2b00b7](https://github.com/kaitranntt/ccs/commit/c2b00b7ad6f01f5d83db6d19e2ceb4e3d10d741b))

### Bug Fixes

* **ci:** run pull request validation on hosted runners ([e7174d6](https://github.com/kaitranntt/ccs/commit/e7174d63bb2ed8b2cf9f013aa69ad178ebbcee4f))
* clarify CLIProxy setup actions ([ac333dd](https://github.com/kaitranntt/ccs/commit/ac333dd1d50538e8164d921099f8ac85cd914999))
* **cliproxy:** explain headless Codex OAuth recovery ([29da75c](https://github.com/kaitranntt/ccs/commit/29da75c0d42ed473029d1222c7fe4e5e655f5b03)), closes [#1213](https://github.com/kaitranntt/ccs/issues/1213)
* **codex:** require local access for dashboard config routes ([bd588a2](https://github.com/kaitranntt/ccs/commit/bd588a271df8be95149d6e87eb302139cb2b4cb9))
* **codex:** self-heal ccsxp cliproxy provider ([#1243](https://github.com/kaitranntt/ccs/issues/1243)) ([4748c45](https://github.com/kaitranntt/ccs/commit/4748c452bd8bc0c18a0a1267324d5ec015a7a578))
* **config:** bind dashboard to loopback by default ([d61469e](https://github.com/kaitranntt/ccs/commit/d61469edcbdd162d9fce04b0aefe6c0506568974))
* ignore internal instance directories in health scans ([667b1d1](https://github.com/kaitranntt/ccs/commit/667b1d1e1f0db7f56f88bb88feaee9e2ce994f5c)), closes [#1236](https://github.com/kaitranntt/ccs/issues/1236)
* **proxy:** avoid exposing local auth token in daemon argv ([#1230](https://github.com/kaitranntt/ccs/issues/1230)) ([a840793](https://github.com/kaitranntt/ccs/commit/a840793a9b091b911afc7aa6ee91666a1c258366))
* **proxy:** strip stale encoding from upstream error responses ([1932b2c](https://github.com/kaitranntt/ccs/commit/1932b2ca61f084629654941efdd09fd1b971efed))
* **proxy:** strip stale encoding from upstream error responses ([#1239](https://github.com/kaitranntt/ccs/issues/1239)) ([54f1aa7](https://github.com/kaitranntt/ccs/commit/54f1aa70c0f16a965608f935ce4a39128e829688))
* **security:** constrain image fallback hook to workspace ([#1232](https://github.com/kaitranntt/ccs/issues/1232)) ([40300c2](https://github.com/kaitranntt/ccs/commit/40300c286ec9f40e89cb2b040a416cba04680930))
* **security:** protect dashboard WebSocket upgrades ([f09cdfc](https://github.com/kaitranntt/ccs/commit/f09cdfcf2ba0b14ffd20f69a1c0923343f245a39))
* **settings:** confine dashboard settings paths ([#1231](https://github.com/kaitranntt/ccs/issues/1231)) ([349db83](https://github.com/kaitranntt/ccs/commit/349db830dfbc1f91b44f287a2aa40426756338a9))
* support Claude subcommands and background sessions ([35b6210](https://github.com/kaitranntt/ccs/commit/35b6210cd2ef3b79d317cd76926b55a126084b1d))
* **ui:** keep codex toml tokens inline ([#1219](https://github.com/kaitranntt/ccs/issues/1219)) ([5138741](https://github.com/kaitranntt/ccs/commit/5138741b5c0fdc0da87982bd59227fad56f8a046))
* **ui:** render codex config as exact text ([#1215](https://github.com/kaitranntt/ccs/issues/1215)) ([225401c](https://github.com/kaitranntt/ccs/commit/225401ceca433705d11c119b3373913c95129c56))
* **ui:** restore codex config highlighting ([#1216](https://github.com/kaitranntt/ccs/issues/1216)) ([e177724](https://github.com/kaitranntt/ccs/commit/e1777247b371749ac717373f0d4299415990413c))
* **ui:** wrap codex config highlight editor ([#1217](https://github.com/kaitranntt/ccs/issues/1217)) ([ba761a0](https://github.com/kaitranntt/ccs/commit/ba761a0d1e75dac9090f58848bf1f0f10d855519))
* **windows:** pin cmd shell for wrapper launches ([#1229](https://github.com/kaitranntt/ccs/issues/1229)) ([9c5d497](https://github.com/kaitranntt/ccs/commit/9c5d4976c51696a86251c507902ea03e4d78a8b0))
2026-05-14 16:55:30 +00:00
Kai (Tam Nhu) TranandGitHub 78c5c85e2b Merge pull request #1244 from kaitranntt/dev
feat: promote dev to main
2026-05-14 12:52:25 -04:00
github-actions[bot] e694f3cd38 chore(release): 7.78.1-dev.8 2026-05-14 16:38:52 +00:00
Kai (Tam Nhu) TranandGitHub 4748c452bd fix(codex): self-heal ccsxp cliproxy provider (#1243) 2026-05-14 12:35:31 -04:00
github-actions[bot] 9748ba4ee0 chore(release): 7.78.1-dev.7 2026-05-14 14:21:39 +00:00
walker1211andGitHub 35b6210cd2 fix: support Claude subcommands and background sessions
Squash merge PR #1240.\n\nValidated locally from the PR head because trusted-author GitHub CI was skipped for this fork PR:\n- bun run validate\n- bun run build:all\n- bun run test:all\n- CCS_E2E_SKIP_BUILD=1 bun run test:e2e
2026-05-14 10:18:23 -04:00
github-actions[bot] e87b627764 chore(release): 7.78.1-dev.6 2026-05-13 13:38:07 +00:00
Kai (Tam Nhu) TranandGitHub 54f1aa70c0 fix(proxy): strip stale encoding from upstream error responses (#1239) 2026-05-13 09:31:43 -04:00
Tam Nhu Tran 1932b2ca61 fix(proxy): strip stale encoding from upstream error responses 2026-05-13 09:13:28 -04:00
github-actions[bot] ab096ac37e chore(release): 7.78.1-dev.5 2026-05-13 02:23:37 +00:00
Kai (Tam Nhu) TranandGitHub 667b1d1e1f fix: ignore internal instance directories in health scans
Closes #1236
2026-05-12 22:17:16 -04:00
github-actions[bot] ac00ba4f00 chore(release): 7.78.1-dev.4 2026-05-12 23:27:59 +00:00
Kai (Tam Nhu) TranandGitHub ac333dd1d5 fix: clarify CLIProxy setup actions
Make account setup the primary CLIProxy CTA, keep advanced variant creation secondary, and contain setup dialogs across narrow viewports.
2026-05-12 19:23:59 -04:00
github-actions[bot] 26311f7752 chore(release): 7.78.1-dev.3 2026-05-12 22:18:38 +00:00
Kai (Tam Nhu) TranandGitHub 40300c286e fix(security): constrain image fallback hook to workspace (#1232) 2026-05-12 18:13:32 -04:00
Kai (Tam Nhu) TranandGitHub 349db830df fix(settings): confine dashboard settings paths (#1231) 2026-05-12 18:13:26 -04:00
Kai (Tam Nhu) TranandGitHub a840793a9b fix(proxy): avoid exposing local auth token in daemon argv (#1230) 2026-05-12 18:13:20 -04:00
Kai (Tam Nhu) TranandGitHub 9c5d4976c5 fix(windows): pin cmd shell for wrapper launches (#1229) 2026-05-12 18:13:13 -04:00
github-actions[bot] 2e5439290e chore(release): 7.78.1-dev.2 2026-05-12 20:49:48 +00:00
Kai (Tam Nhu) TranandGitHub d9b6abf134 Merge pull request #1233 from kaitranntt/kai/feat/1218-claude-subcommand-passthrough
feat(dispatcher): pass through Claude subcommands without interactive-session args
2026-05-12 16:46:12 -04:00
Tam Nhu Tran c2b00b7ad6 feat(dispatcher): pass through Claude subcommands without interactive-session args
Closes #1218.

When the user invokes `ccs <profile> agents` (and other Claude subcommands
like `mcp`, `doctor`, `plugin`, ...), CCS was unconditionally injecting
session-only Claude flags (`--append-system-prompt`, `--disallowedTools`,
`--settings`, official-channels plugin specs) and forwarding the
`DISABLE_TELEMETRY=1` env var from profile settings. Each of those
either errored out the subcommand (`error: unknown option
'--append-system-prompt'`) or silently flipped Claude into
non-interactive list mode, so the new `claude agents` agent view never
opened under CCS.

Add a small `claude-subcommand-detector` that recognizes the documented
Claude subcommand set after skipping known value-taking flags, then have
the three steering-prompt injectors (websearch, image-analysis, browser),
the cliproxy and settings launchers, and the official-channels plan
short-circuit when a subcommand invocation is detected. Also strip
`DISABLE_TELEMETRY` from the spawned env only for subcommand
invocations — upstream Claude Code uses that var as a kill switch for
the subcommand TUIs, and the user's telemetry preference still applies
to every normal interactive session.

Verified end-to-end against `ccs glm agents` and `ccs ck agents`: the
agent view opens correctly, no `unknown option` error, and all 1938
existing tests pass.
2026-05-12 16:35:37 -04:00
github-actions[bot] 72f2f9993d chore(release): 7.78.1-dev.1 2026-05-12 16:50:48 +00:00
Tam Nhu Tran 4c0aa83c82 chore(sync): merge main into dev after release
# Conflicts:
#	.github/workflows/ci.yml
#	package.json
2026-05-12 12:43:02 -04:00