better-ccflare 3.5.43 → 3.5.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -792,12 +792,12 @@ Inspired by [snipeship/ccflare](https://github.com/snipeship/ccflare) - thanks f
792
792
  - [@jw409](https://github.com/jw409) - Fixed OAuth account addition in WSL2 and compiled binaries by replacing unreliable prompt() with readline; systemd deployment guide, BUN_JSC_* crash loop analysis, and preflight environment validator (PR #106)
793
793
  - [@materemias](https://github.com/materemias) - Testing and validation of Vertex AI provider implementation, thorough debugging of OAuth API key authentication (issue #54), requesting and validating AWS Bedrock support (issue #49), extensive testing of new releases and features; fix request details modal hydration race where payload row committed after parent request row, adding lazy re-fetch via `/api/requests/payload/:id` with 404 fallback (PR #186)
794
794
  - [@tqtensor](https://github.com/tqtensor) - Comprehensive memory leak fix preventing OOM kills with smart chunk capping, memory monitoring, and optimized cleanup (PR #67)
795
- - [@lunetics](https://github.com/lunetics) - Force-reset rate limit feature allowing manual clearing of stale rate-limit locks via API, CLI, and dashboard with immediate usage polling (PR #68), OOM kill prevention with periodic data retention cleanup, 3-day default retention, and time-scoped stats queries (PR #70), model registry sync removing retired models and adding sonnet-4.6 CLI shortcut (PR #71); migrated Anthropic usage handling to the new `limits[]` payload shape — per-account display (session/weekly/per-model rows with severity and overage-credit meter), account ranking/utilization and capacity-guard reads, and model-aware proactive throttling so a per-model weekly cap (e.g. Fable) only throttles matching-model requests instead of the whole account, with flat-window fallback throughout ([#296](https://github.com/tombii/better-ccflare/pull/296)); surfaced usage-window exhaustion that was invisible to both `/health` and the accounts dashboard during the 2026-07-09 incident — added a `usage_exhausted` pool counter (subset of `routable`), extracted `rateLimitStatus` display precedence into a tested pure helper (usage exhaustion > unified header snapshot > legacy cooldown > OK), and a shared staleness guard so a known-past window reset is never reported as exhausted on either surface, with characterization tests pinning the incident's account-wide-bench-to-pool-flap chain ([#299](https://github.com/tombii/better-ccflare/pull/299)); fixed the agent interceptor silently rewriting subagent request models to stale/retired ids via a hardcoded alias map, and unconditionally overriding explicit per-call model choices — rewrites now fire only on an explicit DB preference, with a live Anthropic model catalog (`GET /v1/models`, weekly refresh, console-API accounts only by default to avoid OAuth ban risk, passive capture from pass-through traffic) vetting rewrite targets and backing dashboard model dropdowns/validation; added `original_model`/`applied_model` request-row provenance and an `x-better-ccflare-model-rewrite` response header, post-rewrite-aware account/combo routing, agent model provenance (`modelSource`, revert-to-inherit) with a redesigned two-mechanism dashboard edit dialog separating the agent-file default from the runtime preference override ([#300](https://github.com/tombii/better-ccflare/pull/300)); persisted `requires_reauth` detection for dead OAuth refresh tokens after a real incident where two accounts' tokens silently failed for ~43h undetected — preserves the machine-readable OAuth error code ahead of `error_description` in the Anthropic/Codex/xAI providers (previously discarded, so detection could never match), routing exclusion at the single `isAccountAvailable` choke point plus all proactive refresh paths, clear-sites across every reauth flow (CLI, dashboard, Qwen/Codex HTTP handlers), a new `auth_failure` webhook/SSE alert with cooldown dedup, and a dashboard "Needs authentication" badge, backed by ~120 new tests ([#311](https://github.com/tombii/better-ccflare/pull/311)); added opt-in model-scoped capacity routing (`MODEL_SCOPED_CAPACITY_ROUTING=exhausted`) that filters accounts whose per-model-family weekly cap is exhausted, combining telemetry-based `weekly_scoped` usage windows with a reactive 5-minute in-memory negative cache fed by observed `out_of_credits` 429 responses (provenance-tagged `recent_upstream_rejection` vs. `telemetry_confirmed` for honest client messaging), plus a new `session-drain-soonest` load-balancing strategy (`LB_STRATEGY=session-drain-soonest`) that preserves session-stickiness while ranking fresh-selection candidates by earliest future `weekly_all` reset to consume capacity before it expires ([#325](https://github.com/tombii/better-ccflare/pull/325)); Routing settings card on the Settings tab wiring the strategy selector and model-scoped capacity routing toggle from PR #325 to the dashboard — a shared `resolveEnvFileSetting<T>()` resolver backs `getStrategySource()`/`getModelScopedCapacityRoutingSource()` so the env>file>default precedence can't drift between the getter and its source twin, both controls show an env-locked badge and disable themselves when overridden by `LB_STRATEGY`/`MODEL_SCOPED_CAPACITY_ROUTING`, an active-but-unlisted strategy renders as a disabled "(current)" item instead of a blank Select that could be silently overwritten, and a strategy switch now also invalidates the accounts query so the "Primary" badge updates immediately instead of drifting for up to 60s ([#329](https://github.com/tombii/better-ccflare/pull/329)); guarded logger `JSON.stringify` call sites (`Logger.formatMessage`, `LogFileWriter.write`) against circular references and `BigInt` values that previously threw uncaught out of routine `log.debug/info/warn/error` calls into caller code, with a sanitized fallback that preserves `ts`/`level`/`msg` and byte-identical output for normal serializable events ([#330](https://github.com/tombii/better-ccflare/pull/330)); fixed a false auto-pause chain where a legitimately usage-throttled account's `five_hour` window reset triggered an auto-refresh probe, but `applyUsageThrottling` had no exemption for internal synthetic probes and returned better-ccflare's own 529 back to the scheduler, which counted it as a genuine endpoint failure toward `FAILURE_THRESHOLD` — exempted requests carrying the pre-existing `x-better-ccflare-auto-refresh`/`x-better-ccflare-keepalive` loop-prevention headers from usage throttling entirely, and stopped counting a 529 probe response toward the consecutive-failure pause threshold as defense in depth, with TDD regression tests proving both failure modes pre-fix ([#331](https://github.com/tombii/better-ccflare/pull/331)); `docs/routing-architecture.md`, a technical reference documenting the account-selection pipeline end-to-end — the four load-balancing strategies, usage-throttling's pacing-line model, model-scoped capacity routing, and auto-fallback — each with a plain-English explanation and a Mermaid flowchart cross-checked against source, including accuracy fixes from two rounds of Greptile review (combo-routed requests skip model-scoped weekly windows; `out_of_credits`/keepalive 429s don't apply an account cooldown; a request with no `clientSessionId` isn't recorded as a sticky session-affinity mapping; `checkForAutoFallbackAccounts` and `wouldAutoUnpause` are two distinct eligibility rules, not one shared rule) plus a correction for the synthetic-probe throttle exemption landed by PR #331 after this doc branched ([#332](https://github.com/tombii/better-ccflare/pull/332)); fixed the `build:linux-*`/`build:macos-*`/`build:windows-x64` platform-specific build scripts omitting the `--define __BETTER_CCFLARE_VERSION__` flag that the plain `build` script sets, causing locally-built platform binaries to misreport their version via the unrelated `CLAUDE_CLI_VERSION` fallback in `getVersionSync()` ([#333](https://github.com/tombii/better-ccflare/pull/333)); closed a security hole where the `x-better-ccflare-auto-refresh`/`x-better-ccflare-keepalive` internal-probe markers were trusted from any client, letting a forged marker bypass usage-throttling, suppress per-account rate-limit cooldowns, and defeat the 529 in-place retry safety net — gated all probe-marker read-sites behind a process-local secret (`crypto.randomUUID()`, minted at startup and threaded through `ProxyContext`) that only the two schedulers' own loopback probes carry, with the secret stripped from request history, cache-body storage, and the outbound provider request path so it can never leak ([#336](https://github.com/tombii/better-ccflare/pull/336)); separated transient 529-overload cooldown handling from the 429 rate-limit ramp after a production incident where reset-less 529 bursts drove healthy accounts to a 5-minute cooldown ceiling purely on server-side overload signals — a reset-less 529 now takes a fixed short cooldown (`CCFLARE_OVERLOAD_COOLDOWN_MS`) instead of the exponential ramp, a 529-with-reset caps window-scale reset values at overload scale rather than the 429 ceiling, neither 529 variant advances the `consecutive_rate_limits` streak, the single-flight recovery-probe gate arms on the persisted overload reason so an all-529 account still gets a single-flight probe, an all-suppressed candidate pool retries the highest-priority account ungated instead of failing with a hard 503, and the capacity-exhaustion response distinguishes real weekly exhaustion from an eligible account merely cooling down ([#342](https://github.com/tombii/better-ccflare/pull/342))
795
+ - [@lunetics](https://github.com/lunetics) - Force-reset rate limit feature allowing manual clearing of stale rate-limit locks via API, CLI, and dashboard with immediate usage polling (PR #68), OOM kill prevention with periodic data retention cleanup, 3-day default retention, and time-scoped stats queries (PR #70), model registry sync removing retired models and adding sonnet-4.6 CLI shortcut (PR #71); migrated Anthropic usage handling to the new `limits[]` payload shape — per-account display (session/weekly/per-model rows with severity and overage-credit meter), account ranking/utilization and capacity-guard reads, and model-aware proactive throttling so a per-model weekly cap (e.g. Fable) only throttles matching-model requests instead of the whole account, with flat-window fallback throughout ([#296](https://github.com/tombii/better-ccflare/pull/296)); surfaced usage-window exhaustion that was invisible to both `/health` and the accounts dashboard during the 2026-07-09 incident — added a `usage_exhausted` pool counter (subset of `routable`), extracted `rateLimitStatus` display precedence into a tested pure helper (usage exhaustion > unified header snapshot > legacy cooldown > OK), and a shared staleness guard so a known-past window reset is never reported as exhausted on either surface, with characterization tests pinning the incident's account-wide-bench-to-pool-flap chain ([#299](https://github.com/tombii/better-ccflare/pull/299)); fixed the agent interceptor silently rewriting subagent request models to stale/retired ids via a hardcoded alias map, and unconditionally overriding explicit per-call model choices — rewrites now fire only on an explicit DB preference, with a live Anthropic model catalog (`GET /v1/models`, weekly refresh, console-API accounts only by default to avoid OAuth ban risk, passive capture from pass-through traffic) vetting rewrite targets and backing dashboard model dropdowns/validation; added `original_model`/`applied_model` request-row provenance and an `x-better-ccflare-model-rewrite` response header, post-rewrite-aware account/combo routing, agent model provenance (`modelSource`, revert-to-inherit) with a redesigned two-mechanism dashboard edit dialog separating the agent-file default from the runtime preference override ([#300](https://github.com/tombii/better-ccflare/pull/300)); persisted `requires_reauth` detection for dead OAuth refresh tokens after a real incident where two accounts' tokens silently failed for ~43h undetected — preserves the machine-readable OAuth error code ahead of `error_description` in the Anthropic/Codex/xAI providers (previously discarded, so detection could never match), routing exclusion at the single `isAccountAvailable` choke point plus all proactive refresh paths, clear-sites across every reauth flow (CLI, dashboard, Qwen/Codex HTTP handlers), a new `auth_failure` webhook/SSE alert with cooldown dedup, and a dashboard "Needs authentication" badge, backed by ~120 new tests ([#311](https://github.com/tombii/better-ccflare/pull/311)); added opt-in model-scoped capacity routing (`MODEL_SCOPED_CAPACITY_ROUTING=exhausted`) that filters accounts whose per-model-family weekly cap is exhausted, combining telemetry-based `weekly_scoped` usage windows with a reactive 5-minute in-memory negative cache fed by observed `out_of_credits` 429 responses (provenance-tagged `recent_upstream_rejection` vs. `telemetry_confirmed` for honest client messaging), plus a new `session-drain-soonest` load-balancing strategy (`LB_STRATEGY=session-drain-soonest`) that preserves session-stickiness while ranking fresh-selection candidates by earliest future `weekly_all` reset to consume capacity before it expires ([#325](https://github.com/tombii/better-ccflare/pull/325)); Routing settings card on the Settings tab wiring the strategy selector and model-scoped capacity routing toggle from PR #325 to the dashboard — a shared `resolveEnvFileSetting<T>()` resolver backs `getStrategySource()`/`getModelScopedCapacityRoutingSource()` so the env>file>default precedence can't drift between the getter and its source twin, both controls show an env-locked badge and disable themselves when overridden by `LB_STRATEGY`/`MODEL_SCOPED_CAPACITY_ROUTING`, an active-but-unlisted strategy renders as a disabled "(current)" item instead of a blank Select that could be silently overwritten, and a strategy switch now also invalidates the accounts query so the "Primary" badge updates immediately instead of drifting for up to 60s ([#329](https://github.com/tombii/better-ccflare/pull/329)); guarded logger `JSON.stringify` call sites (`Logger.formatMessage`, `LogFileWriter.write`) against circular references and `BigInt` values that previously threw uncaught out of routine `log.debug/info/warn/error` calls into caller code, with a sanitized fallback that preserves `ts`/`level`/`msg` and byte-identical output for normal serializable events ([#330](https://github.com/tombii/better-ccflare/pull/330)); fixed a false auto-pause chain where a legitimately usage-throttled account's `five_hour` window reset triggered an auto-refresh probe, but `applyUsageThrottling` had no exemption for internal synthetic probes and returned better-ccflare's own 529 back to the scheduler, which counted it as a genuine endpoint failure toward `FAILURE_THRESHOLD` — exempted requests carrying the pre-existing `x-better-ccflare-auto-refresh`/`x-better-ccflare-keepalive` loop-prevention headers from usage throttling entirely, and stopped counting a 529 probe response toward the consecutive-failure pause threshold as defense in depth, with TDD regression tests proving both failure modes pre-fix ([#331](https://github.com/tombii/better-ccflare/pull/331)); `docs/routing-architecture.md`, a technical reference documenting the account-selection pipeline end-to-end — the four load-balancing strategies, usage-throttling's pacing-line model, model-scoped capacity routing, and auto-fallback — each with a plain-English explanation and a Mermaid flowchart cross-checked against source, including accuracy fixes from two rounds of Greptile review (combo-routed requests skip model-scoped weekly windows; `out_of_credits`/keepalive 429s don't apply an account cooldown; a request with no `clientSessionId` isn't recorded as a sticky session-affinity mapping; `checkForAutoFallbackAccounts` and `wouldAutoUnpause` are two distinct eligibility rules, not one shared rule) plus a correction for the synthetic-probe throttle exemption landed by PR #331 after this doc branched ([#332](https://github.com/tombii/better-ccflare/pull/332)); fixed the `build:linux-*`/`build:macos-*`/`build:windows-x64` platform-specific build scripts omitting the `--define __BETTER_CCFLARE_VERSION__` flag that the plain `build` script sets, causing locally-built platform binaries to misreport their version via the unrelated `CLAUDE_CLI_VERSION` fallback in `getVersionSync()` ([#333](https://github.com/tombii/better-ccflare/pull/333)); closed a security hole where the `x-better-ccflare-auto-refresh`/`x-better-ccflare-keepalive` internal-probe markers were trusted from any client, letting a forged marker bypass usage-throttling, suppress per-account rate-limit cooldowns, and defeat the 529 in-place retry safety net — gated all probe-marker read-sites behind a process-local secret (`crypto.randomUUID()`, minted at startup and threaded through `ProxyContext`) that only the two schedulers' own loopback probes carry, with the secret stripped from request history, cache-body storage, and the outbound provider request path so it can never leak ([#336](https://github.com/tombii/better-ccflare/pull/336)); separated transient 529-overload cooldown handling from the 429 rate-limit ramp after a production incident where reset-less 529 bursts drove healthy accounts to a 5-minute cooldown ceiling purely on server-side overload signals — a reset-less 529 now takes a fixed short cooldown (`CCFLARE_OVERLOAD_COOLDOWN_MS`) instead of the exponential ramp, a 529-with-reset caps window-scale reset values at overload scale rather than the 429 ceiling, neither 529 variant advances the `consecutive_rate_limits` streak, the single-flight recovery-probe gate arms on the persisted overload reason so an all-529 account still gets a single-flight probe, an all-suppressed candidate pool retries the highest-priority account ungated instead of failing with a hard 503, and the capacity-exhaustion response distinguishes real weekly exhaustion from an eligible account merely cooling down ([#342](https://github.com/tombii/better-ccflare/pull/342)); diagnosed the root cause of orphaned `Response.clone()` tee branches on the 529-overload check path (issue #354) with a comprehensive test suite covering every affected call site (PR #355) — landed as the two still-open header-only `parseRateLimit` clone removals and disposal of the terminal-529 check clone via the existing `drainBody`/`cancelDiscardedResponseBody` helpers (consistent with the Bun no-op-leak finding from PR #353), plus the analogous unreleased `parseUsage` streaming clone in `updateAccountMetadata` ([#355](https://github.com/tombii/better-ccflare/pull/355)); found the same orphaned-tee defect class surviving at the sites #355 deliberately left alone — three body-inspecting error classifiers (`isModelUnavailableError`, `isInvalidThinkingSignatureError`, `isCacheControlRejectionError`) cloned before their content-type gate and stranded that copy on every non-JSON body (ordinary for providers like Qwen), and `OpenAICompatibleProvider.processResponse` had the inversion — its *incoming* response was the orphan, on every successful JSON reply from every openai-compatible provider — fixed by cloning only after the gate and cancelling the discarded original once conversion succeeds, with 9 regression tests pinning both the disposal and the unchanged classifications ([#358](https://github.com/tombii/better-ccflare/pull/358)); fixed `makeProxyRequest` replacing any caller-supplied abort signal with its own timeout-only controller that becomes unreachable once headers arrive, so a client disconnect mid-stream (idle watchdog, Ctrl-C, dropped network) never aborted the upstream fetch — `reader.cancel()` measured as a no-op on Bun, only `abort()` closes the socket, leaking one upstream connection and burning quota for as long as the provider held it open — combined the caller's signal with the header-phase timeout via `AbortSignal.any`, routed every upstream call through one `forwardUpstream` helper that passes the signal explicitly (since provider body transforms rebuild the Request from its URL and drop any signal it carried), and taught the top-level catch to distinguish a client-disconnect `AbortError` from a genuine account failure — returning 499 immediately instead of walking every remaining account and fallback route for a request nobody is listening to ([#360](https://github.com/tombii/better-ccflare/pull/360)); fixed the request-payload endpoint reporting "Request not found" whenever a payload was merely absent — payloads are legitimately missing when capture is disabled or when the usage collector releases long-running requests' payloads after `REQUEST_PAYLOAD_RETENTION_MS` to bound memory, a distinction that cost real debugging time chasing a 340-second request that was in the table the whole time — the 404 now explains a payload isn't stored and why one can legitimately be absent, with the status and JSON shape unchanged ([#362](https://github.com/tombii/better-ccflare/pull/362)); persisted the client session id (body `metadata.user_id`) with each request row after a stream-stall investigation misattributed 43 requests from a locally spawned subagent to a user's test run — the session id was already used for session-affinity routing but never stored, so a session's own subagents were indistinguishable from the session itself in request history; threaded it through the usage collector and all `forwardToClient`/synthetic-failure `saveRequest` call sites with preserve-first `COALESCE` semantics so error-path re-saves can't blank out attribution the main path recorded, sanitized on write (control-character strip, 200-char cap, mirroring `sanitizeProjectName`) since the value was previously only routing state and had no bound on stored length, and fixed the shared `toRequest`/`toRequestResponse` mappers that were silently dropping the field for any caller besides the hand-built HTTP handler — hardened through two rounds of independent adversarial review (Codex, Kimi) that caught a missing session id on 4 of 6 failure-recording paths and the mapper gap, with 9 new tests plus the full database/proxy suite green ([#363](https://github.com/tombii/better-ccflare/pull/363)); surfaced `stream_terminal_state` through `/api/requests` and the live dashboard summary after a day lost diagnosing four stalled requests that all read as healthy `statusCode: 200` — the column was already persisted but no route read it back, so a stream that died mid-content, was cancelled by the client, or was recovered was indistinguishable from a clean response; both write surfaces (REST read path and the live SSE summary) narrow the unconstrained TEXT column through a shared guard instead of an unchecked cast, an unrecognized value surfaces as `"unknown"` rather than collapsing into the same `undefined` a non-streaming request produces, and the narrowing helpers ship from a new cycle-free `@better-ccflare/types/request` subpath since evaluating the package barrel first trips a pre-existing types↔core import cycle — hardened through three rounds of independent adversarial review (Codex, Kimi, Gemini) that caught the unchecked cast, the unknown-state conflation, and an unhardened live-summary path ([#364](https://github.com/tombii/better-ccflare/pull/364))
796
796
  - [@troykelly](https://github.com/troykelly) - Comprehensive PostgreSQL compatibility fixes including boolean type handling, identifier case preservation, BIGINT string coercion, UNION ALL type alignment, HAVING clause compatibility, parameter ordering corrections, worker initialization, and connection pooling (issue #81); detailed bug report and root cause analysis for `/api/accounts` Invalid Date error affecting PostgreSQL BIGINT columns (issue #88)
797
797
  - [@cowwoc](https://github.com/cowwoc) - Compact reliability fixes, space breakdown after cleanup, requests tab without payloads (PR #149); clear stale rate_limited_until when usage API shows capacity restored (PR #150); prevent manually-paused accounts from being selected as auto-fallback candidates (PR #151); balance new sessions by utilization within same-priority accounts using water-filling algorithm (PR #152); fix ModuleNotFound crash in compiled binary when using Compact Database by embedding vacuum-worker at build time (PR #155); expected usage position indicator on rate limit bars showing projected pacing vs. reset window (PR #156); show explicit rate-limited state when usage data unavailable on startup (PR #161); deduplicate concurrent fetchAndCache calls per account to prevent redundant Anthropic requests (PR #159); make GET /api/accounts cache-only and await usage fetch in refresh endpoint, eliminating blind 5s timeout (PR #162); mark account rate-limited when all models exhausted to prevent stale-state retry loops (PR #163); use retry-after header for dynamic model-exhaustion cooldown instead of hardcoded 1 hour (PR #164); remove implicit sonnet catch-all in getModelList preventing silent unexpected model remaps (PR #165); reduce log noise by aggregating auto-unpause skip messages and suppressing identity model mapping logs (PR #167); reasoning effort compatibility layer for OpenAI/Codex routes with deterministic downgrade mapping and count_tokens path support (PR #172, implemented in PR #188); unified Codex model mapping path (PR #203)
798
798
  - [@wonkooklee](https://github.com/wonkooklee) - Fixed ZlibError on startup caused by Bun auto-decompressing upstream 404 responses while leaving stale `content-encoding: gzip` headers intact — applied `withSanitizedProxyHeaders` to the model-not-found early-return path, matching every other forward path (PR #243)
799
799
  - [@Cotch22](https://github.com/Cotch22) - Fixed garbled non-ASCII text in request details modal by decoding base64 bodies as UTF-8 via `TextDecoder` instead of raw `atob()` (PR #246)
800
- - [@zenprocess](https://github.com/zenprocess) - LeastUsed load-balancing strategy selecting accounts by lowest utilization to prevent burst pool-exhaustion (PR #193); fix keepalive 429s incorrectly cooling accounts by skipping cooldown application on synthetic keepalive replays, and shorten the no-reset fallback cooldown from 1 hour to 60 seconds (overridable via env) to prevent mass account lockout on burst contention (PR #196); optional `X-Anthropic-Agent-Id` header for explicit per-agent attribution ([#260](https://github.com/tombii/better-ccflare/pull/260)); detect and record premature SSE termination for all Anthropic-Messages-shaped streams — generalized the terminal-recovery wrapper to cover every anthropic-compatible provider (not just native Anthropic), added a `stream_terminal_state` column distinguishing complete/recovered/error/truncated/client-cancelled outcomes, and fixed success-rate metrics silently recording truncated mid-stream responses as successful ([#344](https://github.com/tombii/better-ccflare/pull/344)); fixed usage-exhausted accounts staying in the selection pool and getting cycled through every model instead of being skipped, by filtering accounts whose representative usage window is fully exhausted out of `getOrderedAccounts()` before the load-balancing strategy ever sees them, with `isAccountAvailable` gaining an optional usage-snapshot parameter so the same exhaustion check backs both the forced-account and combo-slot selection paths ([#345](https://github.com/tombii/better-ccflare/pull/345)); fixed dashboard double-submit creating duplicate accounts and the by-name delete cascade that could wipe every account sharing a name — added a DB-level `UNIQUE(name, provider, custom_endpoint)` guard across all add paths (dashboard, CLI, OAuth), switched account deletion to be genuinely id-scoped everywhere, and added a non-destructive migration that merges existing duplicate rows (credentials, counters, flags, cooldown state) into a single survivor before enforcing uniqueness on both the SQLite and PostgreSQL paths ([#343](https://github.com/tombii/better-ccflare/pull/343)); added MiniMax Token Plan usage polling mirroring the zai/xai fetcher pattern, deriving each window's interval length from its own `end_time - start_time`, inverting the API's remaining-percent into utilization, and using only the `general` model-class row (never falling back to an unrelated quota pool like `video`) so a healthy account can't be misreported as exhausted ([#346](https://github.com/tombii/better-ccflare/pull/346))
800
+ - [@zenprocess](https://github.com/zenprocess) - LeastUsed load-balancing strategy selecting accounts by lowest utilization to prevent burst pool-exhaustion (PR #193); fix keepalive 429s incorrectly cooling accounts by skipping cooldown application on synthetic keepalive replays, and shorten the no-reset fallback cooldown from 1 hour to 60 seconds (overridable via env) to prevent mass account lockout on burst contention (PR #196); optional `X-Anthropic-Agent-Id` header for explicit per-agent attribution ([#260](https://github.com/tombii/better-ccflare/pull/260)); detect and record premature SSE termination for all Anthropic-Messages-shaped streams — generalized the terminal-recovery wrapper to cover every anthropic-compatible provider (not just native Anthropic), added a `stream_terminal_state` column distinguishing complete/recovered/error/truncated/client-cancelled outcomes, and fixed success-rate metrics silently recording truncated mid-stream responses as successful ([#344](https://github.com/tombii/better-ccflare/pull/344)); fixed usage-exhausted accounts staying in the selection pool and getting cycled through every model instead of being skipped, by filtering accounts whose representative usage window is fully exhausted out of `getOrderedAccounts()` before the load-balancing strategy ever sees them, with `isAccountAvailable` gaining an optional usage-snapshot parameter so the same exhaustion check backs both the forced-account and combo-slot selection paths ([#345](https://github.com/tombii/better-ccflare/pull/345)); fixed dashboard double-submit creating duplicate accounts and the by-name delete cascade that could wipe every account sharing a name — added a DB-level `UNIQUE(name, provider, custom_endpoint)` guard across all add paths (dashboard, CLI, OAuth), switched account deletion to be genuinely id-scoped everywhere, and added a non-destructive migration that merges existing duplicate rows (credentials, counters, flags, cooldown state) into a single survivor before enforcing uniqueness on both the SQLite and PostgreSQL paths ([#343](https://github.com/tombii/better-ccflare/pull/343)); added MiniMax Token Plan usage polling mirroring the zai/xai fetcher pattern, deriving each window's interval length from its own `end_time - start_time`, inverting the API's remaining-percent into utilization, and using only the `general` model-class row (never falling back to an unrelated quota pool like `video`) so a healthy account can't be misreported as exhausted ([#346](https://github.com/tombii/better-ccflare/pull/346)); fixed the dashboard's `no_account` bucket always reporting 0% success and being un-clickable — `COALESCE(account_used, ...)` in the success-rate query and an `IS NULL OR = ...` drill-down filter so NULL-encoded and legacy `'no_account'`-literal rows both collapse into the same bucket, plus a self-reported PostgreSQL production incident (GROUP BY functional-dependency violation from the placeholder renumberer) diagnosed and fixed same-day with a verified `GROUP BY 1` rewrite, BIGINT-as-string coercions across several repositories, and a new `DATABASE_URL`-gated live PostgreSQL query harness ([#352](https://github.com/tombii/better-ccflare/pull/352)); hardened MiniMax Token Plan status-code handling — replaced the `!== 0` failure check with a positive success allowlist distinguishing known failures (auth/quota) from unrecognized codes so a future MiniMax success code other than `0` can't silently read as a permanent failure, and fixed `registerMinimaxUsagePolling` silently skipping accounts with a missing/empty API key with no log signal, which previously surfaced as a misleading "no Minimax accounts found" diagnosis when accounts existed but lacked credentials ([#350](https://github.com/tombii/better-ccflare/pull/350)); gated agent workspace auto-discovery in `interceptAndModifyRequest` behind an opt-in `BETTER_CCFLARE_AGENT_WORKSPACE_AUTODISCOVER` flag (off by default), mirroring the existing `BETTER_CCFLARE_DISCOVER_PLUGIN_AGENTS` precedent — stops every request in containerized/server deploys from regex-hunting filesystem paths out of client system prompts and burning a hot-path `existsSync` + `registerWorkspace()` cache refresh that could never succeed against the local allowlist anyway, while already-persisted workspaces continue to load normally ([#361](https://github.com/tombii/better-ccflare/pull/361)); fixed Bun's native off-heap body retention (issue #273) on abandoned proxy `Response`s — benchmarked and disproved the prior `body.cancel()` mitigation as a no-op on every released Bun (indistinguishable RSS growth with or without it, confirmed upstream at oven-sh/bun#35093), then replaced it with a chunked drain (`drainBody`) that actually releases the native backing store, wired across all 12 failover/retry discard sites plus the previously-unreleased `extractUsageInfo` response clone in `updateAccountMetadata`; also caught and disclosed a flaw in the PR's own real-network leak harness after merge (bad auth token producing 144-byte responses, plus shared-process RSS measurement masking the signal), replacing it with an isolated-process rig and cautioning that its own median result is inconclusive at n=6 ([#353](https://github.com/tombii/better-ccflare/pull/353)); made the pool-exhausted 503 usage-aware after a production incident where every account was usage-capped (not rate-limited) for 116 minutes — `/health` kept reporting `routable: 3` and every 503 carried a flat `Retry-After: 60`, which combined with `CLAUDE_CODE_MAX_RETRIES=5` to kill client retries in ~300s; added a `usage_exhausted` reason to the pool-exhausted response ladder, derived `next_available_at`/`Retry-After` from the earliest of cooldown expiry and known usage reset with a 600s (UsageCache TTL) floor when no reset is known, and made `/health`'s `routable` count usage-aware to match what the proxy will actually route ([#365](https://github.com/tombii/better-ccflare/pull/365)); added a per-account/per-provider circuit breaker (closed/open/half-open) around the proxy's failure handling, tripping on sustained upstream failures independently of rate-limit/usage state and exposing a distinct `circuit_open` pool-exhaustion reason with its own 30s Retry-After cooldown floor — composed against the concurrently-landed usage-aware pool-exhaustion work (#365) so `circuit_open` still reports the longer, more honest wait when a breaker-tripped account also has a known usage or rate-limit recovery time, rather than undercutting it with the bare 30s floor ([#349](https://github.com/tombii/better-ccflare/pull/349)); surfaced full anomaly-detector totals and truncation state in the Insights dashboard — a new per-detector `AnomalyDetectorSummary` (`totalCount`/`truncated`) exposes the untrimmed detected count alongside the existing top-N sample so an operator can tell "50 of 50" (healthy) from "50 of 3,247" (a real spike) instead of a silently saturated cap, with a new `AnomaliesView` panel surfacing severity ordering, a truncation badge, and threshold text derived live from response `meta` rather than hard-coded; also fixed a self-caught regression where sanitizing the `project` field for display before anomaly detection collapsed distinct same-63-char-prefix projects into one runaway-loop group — moved the sanitizer to the output boundary so detection always sees the raw, already-length-capped DB value ([#369](https://github.com/tombii/better-ccflare/pull/369)); added an optional build-time `CCFLARE_BUILD_SUFFIX` env var so patched/forked builds report `<version>+<suffix>` instead of an indistinguishable stock version string, surfaced through the CLI `--version` flag, the server startup banner, and the dashboard bundle, with a strict no-op (byte-identical output) when unset ([#371](https://github.com/tombii/better-ccflare/pull/371)); fixed MiniMax Token Plan and Alibaba Coding Plan accounts collapsing to a single most-restrictive usage bar instead of separate 5-hour/7-day rows — added the missing providers to the dashboard's weekly-usage allow-list and a shape-based probe in `RateLimitProgress` disambiguating MiniMax's canonical camelCase `resetAt` windows from the Anthropic-style snake_case `resets_at` shape, so each window now renders its own utilization instead of the top-level `Math.max`-across-windows fallback hiding real headroom ([#372](https://github.com/tombii/better-ccflare/pull/372))
801
801
  - [@issmirnov](https://github.com/issmirnov) - Persisted analytics view controls (view mode, time range, metric, model breakdown, filters) in the URL query string and localStorage so selections survive reloads and are shareable via link, with defensive decoding and cumulative-view coupling enforced centrally (PR #252); round the Success Rate Trend chart's Y-axis tick labels and tooltip to zero decimals via `formatPercentage(..., 0)` so values like `99.1666...%` no longer overflow their container, and add a human-readable "Success Rate" line name (PR #280)
802
802
  - [@CorentinLumineau](https://github.com/CorentinLumineau) - Plugin agent discovery scanning Claude Code plugin directories for agent definitions alongside the built-in agents path, with manifest parsing, name validation, and dashboard display (PR #197)
803
803
  - [@d4rken](https://github.com/d4rken) - fixed dashboard "Active" badge showing the most-recently-used account instead of the one the load balancer would actually pick next — introduced a side-effect-free `peek()` to `LoadBalancingStrategy` (shared `peek-availability.ts` mirrors auto-unpause logic without DB writes), dashboard accounts endpoint stamps `isPrimary: true` on the predicted pick, badge renamed from "Active" to "Primary"; also inlined the account-row controls previously hidden behind a `MoreHorizontal` dropdown into flat action buttons (PR #218); Dashboard account row reorganization into wrap-friendly multi-row layout with overflow kebab menu, restored tooltip context for moved actions, and deduped re-auth condition (PR #204); surfaced API key mutation errors inline in create/delete/toggle dialogs, prevented first-key lockout by disabling admin checkbox during loading with fail-safe isFirstKey guard, and added dismissible error banner for role/toggle failures (PR #205); fixed OAuth account creation silently discarding the priority value by adding a priority column to oauth_sessions and threading it through init → session → callback → account creation, plus replaced the constraint-dropping `CREATE TABLE … AS SELECT` tier-drop rebuild with an explicit schema-preserving migration (PR #206); surfaced update-check failure reason in dashboard tile by including the underlying error message in the backend 500 response body, switching to structured Logger, and displaying the error message under "Check Failed" in the frontend sidebar tile (PR #207); exempted /api/version/check from authentication so the dashboard update tile works correctly when API keys are configured (PR #208); replaced bare error-code list with grouped error UI featuring per-(errorCode, account) dismissal, time-window selector (1h/24h/7d/all) persisted in localStorage, clickable rows opening a details modal with human-readable descriptions and recovery info, and a CTE+window-function backend query joining account rate-limit state (PR #209); added combined-quota "5h Pool" and "7d Pool" metric tiles to the Overview dashboard aggregating per-account utilization across all eligible accounts, with worst-account subline, popover breakdown by contributing/exhausted/excluded/fallback accounts, at-risk projection, and next-reset time (PR #210); fixed SIGTERM race where interval-manager's synchronous process.exit(0) short-circuited async shutdown, dropping in-flight requests and pending DB writes — removed the early exit, added HTTP drain via serverInstance.stop(), reordered shutdown sequence (schedulers → HTTP drain → usage worker → DB flush), and added a 30s watchdog to bound total shutdown time (PR #214); fixed API key copy button silently failing on plain-HTTP hosts by adding a `copyText` helper that prefers the async Clipboard API and falls back to an off-screen textarea + `execCommand` for non-secure contexts, and routed `CopyButton` and the inline copy buttons in `ApiKeysTab` through it so the checkmark animation works in both cases (PR #215); fixed post-processor worker timing out on large databases (multi-GB) by passing `fastMode=true` to skip the redundant PRAGMA integrity_check — the main thread already ran it, and re-running it in the worker blew past the 10s startup deadline causing silent request loss (PR #220); replaced the bare recent-errors string list with a grouped error card featuring per-(errorCode, account) dismissal, time-window selector (1h/24h/7d/all), clickable rows opening a details modal with human-readable descriptions, recovery info, and provider-aware copy for model_fallback_429 that escalates to error only when no other accounts are available (PR #212); redesigned request history row layout into a two-row card that never wraps the top-line summary, switched the list view to the summary-only endpoint to eliminate the ~12MB per-page-load body fetch, and fixed the API key and account filter dropdowns to source options from dedicated endpoints so deleted-but-historical entries remain selectable (PR #221); replaced fs.copyFileSync migration backup with VACUUM INTO + atomic rename via a .partial temp file, producing a defragmented consistent snapshot and ensuring the named backup only appears complete (PR #222); added bounded backup retention — after each migration backup, prunes `.backup.<ts>` files to keep the newest N (default 3, configurable via `BETTER_CCFLARE_MIGRATION_BACKUP_KEEP`, 0 to disable), sorting by name-embedded timestamp to survive rsync/clock-drift, skipping operator-renamed files with non-integer suffixes (PR #223); bumped the post-processor worker startup timeout from 10s to 60s and made it overridable via `CF_WORKER_STARTUP_TIMEOUT_MS`, preventing silent analytics loss on large (multi-GB) databases where the per-handle PRAGMA work blew past the old deadline (PR #224); narrowed the migration backup gate from a 41-condition OR to only the three genuinely irreversible operations (refresh_token NOT NULL rebuild, account_tier drop, oauth_sessions.tier drop), eliminating multi-GB backup copies on every restart when only additive ALTER TABLE ADD COLUMN migrations are pending (PR #225); resolved all 42 error-level Biome diagnostics across dashboard components, hooks, and openai-formats tests — replaced `noNonNullAssertedOptionalChain` patterns in tests with explicit non-null assertions after `toBeDefined()`, converted `forEach` callbacks to `for…of` to fix `useIterableCallbackReturn`, added scoped `biome-ignore` comments with explanations for `noArrayIndexKey` cases where stable ids aren't available, and fixed two a11y errors in ErrorBoundary (SVG role/aria-label/title, button type attribute) (PR #226); replaced the blocking startup `PRAGMA integrity_check` (which froze the event loop for 94s on a 7.6 GiB DB) with a dual-timer background scheduler running `quick_check` every 6h and a full `integrity_check` + `foreign_key_check` every 24h in a dedicated `bun:sqlite` worker, added a sticky-corrupt rule so a quick `ok` cannot mask a full corruption finding, exposed the status via an expanded `/api/storage` response and a new `POST /api/storage/integrity/check` on-demand endpoint, and surfaced it in the Overview dashboard via a `StorageIntegrityCard` and sticky `StorageIntegrityBanner` on corruption — also adds `foreign_key_check` to the full probe since `integrity_check` per SQLite docs does not verify foreign keys (PR #227); fixed Logger.{error,warn,info,debug} silently emitting `{}` for Error instances by normalizing Errors to plain objects with name/message/stack/cause before serialization — affects 159 call sites including the "Failed to intercept/modify request" line in agent-interceptor — and demoted all per-request INFO logs in agent-interceptor and extractSystemPrompt to DEBUG, plus skipped the spurious `~/.claude/.claude/agents` path construction for global CLAUDE.md entries since global agents are already loaded unconditionally by AgentRegistry (PR #228); fixed `LOG_LEVEL=DEBUG` being silently ignored — `LogLevel.DEBUG === 0` is falsy so `getLogLevelFromEnv() || level` fell back to the constructor default (INFO), suppressing all debug output even when explicitly configured; switched to `??` so the env override applies for DEBUG too, and added regression tests covering DEBUG/WARN/ERROR/unset/unknown values plus the `silentConsole` side-effect (PR #229); eliminated the blocking full VACUUM trap — hourly retention cleanup and "Compact now" called `incrementalVacuum` which silently fell back to a live full VACUUM on existing DBs (auto_vacuum≠INCREMENTAL), freezing the proxy for minutes every hour — replaced with: `configureSqlite` and `ensureSchema` set `PRAGMA auto_vacuum = INCREMENTAL` on fresh DBs, new `bootstrapAutoVacuum()` migrates existing mode-0 DBs once at startup before HTTP binds (one-time VACUUM cost, never blocks live traffic), `incrementalVacuum()` now dispatches `PRAGMA incremental_vacuum(N)` to a worker thread on mode-2 DBs or logs and returns otherwise (no destructive fallback), drops hourly N from 200000 to 8000 (~32 MiB) to keep writer-slot hold sub-100ms, adds consecutive-skip escalation after 3 missed ticks, and gates the auto_vacuum PRAGMA on mode=0 only to preserve operator-set FULL mode; also removed the dashboard "Compact now" button/endpoint (same hang trap from a different surface) while retaining `bun run cli --compact` with a writer-lock probe that refuses if a running service holds it; and dropped the stale `fastMode` constructor arg from DatabaseOperations after PR #227's integrity-scheduler rework made it redundant (PR #230); fixed `mmapSize=0` being silently ignored in `configureSqlite` — the `> 0` guard meant the default `mmapSize: 0` fell through to bun:sqlite's built-in mmap default (~15 GiB observed on a 15 GiB DB), causing the OOM-killing VACUUM scenario from #230 to recur; also applied memory-bounded PRAGMAs (`mmap_size=0`, `cache_size=-2000`, `temp_store=FILE`) to the full-VACUUM worker thread opened by `compact()` / `--compact`, which had the same gap (PR #231); bounded AsyncDbWriter retention to stop a post-processor worker memory leak — split the single job queue into separate metadata (count-capped at 2000) and payload (count-capped at 1000, byte-capped at 100 MB) queues, added a MAX_JOBS_PER_TICK=50 / MAX_DRAIN_MS_PER_TICK=250ms dual budget so processQueue yields to the event loop instead of monopolizing it, added round-robin scheduling to prevent starvation between queue types, added canAcceptPayload() preflight to skip serialization under backpressure, and surfaced per-queue lengths, byte budget, oldest-job age, and per-kind drop counters in getHealth() (PR #234); enabled the refresh-usage button for Codex OAuth accounts — sends one minimal `/responses` request (bounded to `max_output_tokens: 1`, `reasoning.effort: "minimal"`) to capture `x-codex-*` rate-limit headers, cancels the response body after the header snapshot, deduplicates concurrent clicks per-account with a shared in-flight promise, and wires the result into the existing usageCache + accounts.rate_limit_reset flow; polling intentionally excluded to avoid burning quota at 90s cadence (PR #219); added "Avg / day" and "Avg / week" burn-rate sub-rows to the Plan Value metric card on the Overview dashboard — computed server-side from fixed 7-day and 30-day windows (filter-independent) with a clamped divisor via `effectiveBurnRateDays` so thin history doesn't inflate averages, plus a generic `subRows` prop on `MetricCard` for the UI (PR #235); routed Anthropic 529 overloaded_error responses through the existing rate-limit failover machinery so configured fallback providers take over during an overload instead of forwarding the 529 directly to the client — `parseRateLimit` treats 529 as rate-limited, parsing Retry-After (delta-seconds and HTTP-date) and x-ratelimit-reset with a `clampResetTime` helper that caps reset times to 24h to prevent hostile headers from permanently disabling an account; mid-stream `SseRateLimitSniffer` fires on `overloaded_error` frames for Anthropic-shape providers (anthropic/claude-oauth) only with a line-anchored regex to prevent false positives; two new `RateLimitReason` values (`upstream_529_overloaded_with_reset` / `upstream_529_overloaded_no_reset`) surface accurate audit attribution in the dashboard; `returnRateLimitedResponseOnExhaustion` forwards the original 529 upstream response to the client when the full account pool is exhausted so callers see the real error instead of a generic 503 (PR #236); fixed auto-refresh scheduler probing manually-paused accounts in an endless loop — the eligibility SQL excluded paused accounts only when `auto_pause_on_overage_enabled=0`, ignoring `pause_reason`, while the auto-resume guard in `sendDummyMessage` only un-pauses accounts where `pause_reason IN (NULL,'overage')`; aligned the SQL eligibility query to a positive allowlist (`paused=0 OR (overage_enabled=1 AND pause_reason IN (NULL,'overage'))`) and tightened `isOveragePaused` in `account-selector.ts` to require the same condition, so manual, failure-threshold, and peak_hours pauses are left completely alone; adds a regression test that re-executes the live scheduler SQL against a seeded in-memory SQLite DB covering all seven pause-reason scenarios (PR #237); replaced flat per-account 429 cooldown with adaptive exponential backoff — `consecutive_rate_limits` counter increments atomically in SQL on each 429 and drives `min(upstream_reset, BASE × 2^(n−1))` cooldown (capped at MAX, default 30s base / 5min max), counter resets after 5min of clean operation or on successful token refresh, all four 429 paths (response-processor, model_fallback_429, all_models_exhausted_429, mid-stream SSE sniffer) route through a single `applyRateLimitCooldown` helper, tunables overridable via `CCFLARE_RATE_LIMIT_BACKOFF_BASE_MS` / `CCFLARE_RATE_LIMIT_BACKOFF_MAX_MS` / `CCFLARE_RATE_LIMIT_RESET_STABILITY_MS` (PR #213)
@@ -806,6 +806,7 @@ Inspired by [snipeship/ccflare](https://github.com/snipeship/ccflare) - thanks f
806
806
  - [@robsonek](https://github.com/robsonek) — Usage History dashboard tab: per-account, per-window usage utilization time series with server-computed exhaustion prediction, persisted via a callback-injected snapshot hook on the existing `/oauth/usage` poll (no new Anthropic API calls), dual SQLite/PostgreSQL migration, retention pruning, and jitter-tolerant window segmentation ([#294](https://github.com/tombii/better-ccflare/pull/294))
807
807
  - [@flex-seongmin](https://github.com/flex-seongmin) — Explicit outbound forward-proxy support (`BETTER_CCFLARE_OUTBOUND_PROXY`) for enterprise TLS-intercepting egress gateways — a global Bun fetch wrapper installed at both server and CLI startup so provider requests, OAuth flows, usage polling, and webhooks are all covered without threading a `proxy` option through every call site, with always-exempt loopback destinations (full `127.0.0.0/8` range), caller-supplied `proxy` precedence, strict no-op when unset, and credential-safe logging on both the enable path and invalid-URL rejection path ([#334](https://github.com/tombii/better-ccflare/pull/334))
808
808
  - [@CooLowbro](https://github.com/CooLowbro) — Opt-in guard (`CCFLARE_DISABLE_COMBO_SESSION_FALLBACK`) preventing exhausted combo-routed requests from silently falling through into normal `SessionStrategy` routing, so isolated combo pools (e.g. Anthropic-only vs. Codex-only) stay isolated instead of spilling over — applied at both the account-selection prefilter and the post-failover fallback point, with rejected requests still recorded through the usage collector as `combo_session_fallback_disabled` so failure metrics remain visible ([#339](https://github.com/tombii/better-ccflare/pull/339))
809
+ - [@vansh2408](https://github.com/vansh2408) — Fixed Codex-routed requests failing with `400 Invalid JSON schema: regex lookaround is not supported` whenever an MCP tool's input schema used a regex `pattern` with lookaround (e.g. an email-validation tool) — extended the existing OpenAI schema sanitizer (renamed `removeUriFormat` → `sanitizeSchemaForOpenAI`) with a lookaround-detecting rule that safely drops the unsupported `pattern` while preserving the field's `type: "string"`, and applied it to the Codex provider's tool conversion, which also brought the pre-existing `$schema`/`format: "uri"` scrubbing to that path for the first time ([#359](https://github.com/tombii/better-ccflare/pull/359))
809
810
 
810
811
  ## Contributing
811
812
 
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-ccflare",
3
- "version": "3.5.43",
3
+ "version": "3.5.45",
4
4
  "description": "Load balancer proxy for Claude API with intelligent distribution across multiple OAuth accounts to avoid rate limiting",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,13 +17,13 @@
17
17
  "type": "module",
18
18
  "scripts": {
19
19
  "dev": "bun run src/main.ts",
20
- "build": "mkdir -p dist && bun -e \"const fs=require('fs'); if (!fs.existsSync('../../packages/database/src/inline-vacuum-worker.ts')) { fs.writeFileSync('../../packages/database/src/inline-vacuum-worker.ts', 'export const EMBEDDED_VACUUM_WORKER_CODE = \\\"\\\";'); }\" && bun -e \"const fs=require('fs'); if (!fs.existsSync('../../packages/database/src/inline-incremental-vacuum-worker.ts')) { fs.writeFileSync('../../packages/database/src/inline-incremental-vacuum-worker.ts', 'export const EMBEDDED_INCREMENTAL_VACUUM_WORKER_CODE = \\\"\\\";'); }\" && bun -e \"const fs=require('fs'); if (!fs.existsSync('../../packages/database/src/inline-integrity-check-worker.ts')) { fs.writeFileSync('../../packages/database/src/inline-integrity-check-worker.ts', 'export const EMBEDDED_INTEGRITY_CHECK_WORKER_CODE = \\\"\\\";'); }\" && bun build ../../packages/database/src/vacuum-worker.ts --outfile dist/vacuum-worker.js --target=bun --minify && bun -e \"const fs=require('fs'); const code=fs.readFileSync('dist/vacuum-worker.js','utf8'); const encoded=Buffer.from(code).toString('base64'); fs.writeFileSync('../../packages/database/src/inline-vacuum-worker.ts', 'export const EMBEDDED_VACUUM_WORKER_CODE = \\\"' + encoded + '\\\";');\" && bun build ../../packages/database/src/incremental-vacuum-worker.ts --outfile dist/incremental-vacuum-worker.js --target=bun --minify && bun -e \"const fs=require('fs'); const code=fs.readFileSync('dist/incremental-vacuum-worker.js','utf8'); const encoded=Buffer.from(code).toString('base64'); fs.writeFileSync('../../packages/database/src/inline-incremental-vacuum-worker.ts', 'export const EMBEDDED_INCREMENTAL_VACUUM_WORKER_CODE = \\\"' + encoded + '\\\";');\" && bun build ../../packages/database/src/integrity-check-worker.ts --outfile dist/integrity-check-worker.js --target=bun --minify && bun -e \"const fs=require('fs'); const code=fs.readFileSync('dist/integrity-check-worker.js','utf8'); const encoded=Buffer.from(code).toString('base64'); fs.writeFileSync('../../packages/database/src/inline-integrity-check-worker.ts', 'export const EMBEDDED_INTEGRITY_CHECK_WORKER_CODE = \\\"' + encoded + '\\\";');\" && rm -f dist/better-ccflare && bun build src/main.ts --compile --outfile dist/better-ccflare --target=bun --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")'\\\"' && rm -f dist/vacuum-worker.js dist/incremental-vacuum-worker.js dist/integrity-check-worker.js",
20
+ "build": "case \"$CCFLARE_BUILD_SUFFIX\" in \"\"|*[!a-zA-Z0-9._-]*) if [ -n \"$CCFLARE_BUILD_SUFFIX\" ]; then echo 'CCFLARE_BUILD_SUFFIX must only contain alphanumeric characters, dots, underscores, and hyphens' >&2; exit 1; fi ;; esac && mkdir -p dist && bun -e \"const fs=require('fs'); if (!fs.existsSync('../../packages/database/src/inline-vacuum-worker.ts')) { fs.writeFileSync('../../packages/database/src/inline-vacuum-worker.ts', 'export const EMBEDDED_VACUUM_WORKER_CODE = \\\"\\\";'); }\" && bun -e \"const fs=require('fs'); if (!fs.existsSync('../../packages/database/src/inline-incremental-vacuum-worker.ts')) { fs.writeFileSync('../../packages/database/src/inline-incremental-vacuum-worker.ts', 'export const EMBEDDED_INCREMENTAL_VACUUM_WORKER_CODE = \\\"\\\";'); }\" && bun -e \"const fs=require('fs'); if (!fs.existsSync('../../packages/database/src/inline-integrity-check-worker.ts')) { fs.writeFileSync('../../packages/database/src/inline-integrity-check-worker.ts', 'export const EMBEDDED_INTEGRITY_CHECK_WORKER_CODE = \\\"\\\";'); }\" && bun build ../../packages/database/src/vacuum-worker.ts --outfile dist/vacuum-worker.js --target=bun --minify && bun -e \"const fs=require('fs'); const code=fs.readFileSync('dist/vacuum-worker.js','utf8'); const encoded=Buffer.from(code).toString('base64'); fs.writeFileSync('../../packages/database/src/inline-vacuum-worker.ts', 'export const EMBEDDED_VACUUM_WORKER_CODE = \\\"' + encoded + '\\\";');\" && bun build ../../packages/database/src/incremental-vacuum-worker.ts --outfile dist/incremental-vacuum-worker.js --target=bun --minify && bun -e \"const fs=require('fs'); const code=fs.readFileSync('dist/incremental-vacuum-worker.js','utf8'); const encoded=Buffer.from(code).toString('base64'); fs.writeFileSync('../../packages/database/src/inline-incremental-vacuum-worker.ts', 'export const EMBEDDED_INCREMENTAL_VACUUM_WORKER_CODE = \\\"' + encoded + '\\\";');\" && bun build ../../packages/database/src/integrity-check-worker.ts --outfile dist/integrity-check-worker.js --target=bun --minify && bun -e \"const fs=require('fs'); const code=fs.readFileSync('dist/integrity-check-worker.js','utf8'); const encoded=Buffer.from(code).toString('base64'); fs.writeFileSync('../../packages/database/src/inline-integrity-check-worker.ts', 'export const EMBEDDED_INTEGRITY_CHECK_WORKER_CODE = \\\"' + encoded + '\\\";');\" && rm -f dist/better-ccflare && bun build src/main.ts --compile --outfile dist/better-ccflare --target=bun --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")$([ -n \"$CCFLARE_BUILD_SUFFIX\" ] && printf \"+%s\" \"$CCFLARE_BUILD_SUFFIX\")'\\\"' && rm -f dist/vacuum-worker.js dist/incremental-vacuum-worker.js dist/integrity-check-worker.js",
21
21
  "build:multi": "bun run build-multi-arch.ts",
22
- "build:linux-amd64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-linux-amd64 --target=bun-linux-amd64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")'\\\"'",
23
- "build:linux-arm64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-linux-arm64 --target=bun-linux-arm64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")'\\\"'",
24
- "build:macos-x86_64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-macos-x86_64 --target=bun-darwin-x64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")'\\\"'",
25
- "build:macos-arm64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-macos-arm64 --target=bun-darwin-arm64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")'\\\"'",
26
- "build:windows-x64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-windows-x64.exe --target=bun-windows-x64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")'\\\"'",
22
+ "build:linux-amd64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-linux-amd64 --target=bun-linux-amd64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")$([ -n \"$CCFLARE_BUILD_SUFFIX\" ] && printf \"+%s\" \"$CCFLARE_BUILD_SUFFIX\")'\\\"'",
23
+ "build:linux-arm64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-linux-arm64 --target=bun-linux-arm64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")$([ -n \"$CCFLARE_BUILD_SUFFIX\" ] && printf \"+%s\" \"$CCFLARE_BUILD_SUFFIX\")'\\\"'",
24
+ "build:macos-x86_64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-macos-x86_64 --target=bun-darwin-x64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")$([ -n \"$CCFLARE_BUILD_SUFFIX\" ] && printf \"+%s\" \"$CCFLARE_BUILD_SUFFIX\")'\\\"'",
25
+ "build:macos-arm64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-macos-arm64 --target=bun-darwin-arm64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")$([ -n \"$CCFLARE_BUILD_SUFFIX\" ] && printf \"+%s\" \"$CCFLARE_BUILD_SUFFIX\")'\\\"'",
26
+ "build:windows-x64": "bun run build && bun build src/main.ts --compile --outfile dist/better-ccflare-windows-x64.exe --target=bun-windows-x64 --minify --define __BETTER_CCFLARE_VERSION__='\\\"'$(node -p \"require('./package.json').version\")$([ -n \"$CCFLARE_BUILD_SUFFIX\" ] && printf \"+%s\" \"$CCFLARE_BUILD_SUFFIX\")'\\\"'",
27
27
  "prepublishOnly": "cp ../../README.md . && bun run --cwd ../.. build:dashboard && bun run build && jq 'del(.dependencies[\"@better-ccflare/cli-commands\", \"@better-ccflare/config\", \"@better-ccflare/core\", \"@better-ccflare/core-di\", \"@better-ccflare/database\", \"@better-ccflare/logger\", \"@better-ccflare/server\"])' package.json > package.json.tmp && mv package.json.tmp package.json",
28
28
  "postpublish": "chmod +x dist/better-ccflare"
29
29
  },