From 1e7ad7e75e2edd1e36fe9651efd44c464b7f8375 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Tue, 9 Jun 2026 18:04:53 -0400 Subject: [PATCH] feat(bar): add Get CCS Bar dashboard banner and ccs bar docs page Add a 'Get CCS Bar' promo banner + card to the dashboard (mirroring the OpenRouter promo pattern + design system) so users discover the macOS menu-bar app, with a macOS-aware install CTA. Add a user-facing docs/ccs-bar.md covering what it is, install via 'ccs bar install', launch, what it shows, uninstall, and the loopback requirement -- closing the docs-sync gap for the new ccs bar command. --- docs/ccs-bar.md | 84 +++++++++++++++ ui/src/components/profiles/ccs-bar-banner.tsx | 101 ++++++++++++++++++ .../profiles/ccs-bar-promo-card.tsx | 53 +++++++++ ui/src/components/profiles/index.ts | 5 + ui/src/lib/i18n.ts | 11 ++ ui/src/pages/api.tsx | 13 +++ 6 files changed, 267 insertions(+) create mode 100644 docs/ccs-bar.md create mode 100644 ui/src/components/profiles/ccs-bar-banner.tsx create mode 100644 ui/src/components/profiles/ccs-bar-promo-card.tsx diff --git a/docs/ccs-bar.md b/docs/ccs-bar.md new file mode 100644 index 00000000..82bb9a47 --- /dev/null +++ b/docs/ccs-bar.md @@ -0,0 +1,84 @@ +# CCS Bar — Native macOS Menu Bar App + +CCS Bar is a native macOS menu-bar app that shows live subscription quota and usage at a glance for your Claude Code, Codex, and CLIProxy accounts, without opening the dashboard. + +## What It Is + +CCS Bar is a thin client of the CCS local web-server. It never talks to a provider directly: every call goes to `localhost`, and CCS performs any provider fetch server-side. Opening the menu fires a debounced force-refresh so the glance reflects live data without blocking the UI. + +It is macOS only. + +## What It Shows + +- Per-account quota percent and reset countdown +- Account tier +- Today, 7-day, and 30-day cost +- A 30-day usage sparkline +- Account state (active, paused, default) +- Native subscription rows for Claude Code and Codex + +## Requirements + +- macOS +- CCS CLI installed and configured (`ccs config` works) +- The CCS web-server reachable on loopback. `ccs bar` (or `ccs bar launch`) starts it for you. + +## Install + +```bash +ccs bar install +``` + +This downloads `CCS-Bar.app.zip` from the floating `ccs-bar-latest` GitHub release and installs `CCS Bar.app` into `~/Applications`. Downloads are restricted to `github.com` and `objects.githubusercontent.com`, and extraction is guarded against zip-slip. + +### Gatekeeper note + +The v1 builds use ad-hoc signing, so the first launch may be blocked by Gatekeeper. Either right-click the app and choose Open, or clear the quarantine attribute: + +```bash +xattr -dr com.apple.quarantine "$HOME/Applications/CCS Bar.app" +``` + +## Launch + +```bash +ccs bar # alias: ccs bar launch +``` + +This makes sure the web-server is up, writes the discovery file `~/.ccs/bar.json`, and opens the app. The discovery file looks like this: + +```json +{ "baseUrl": "http://127.0.0.1:3000", "port": 3000, "authMode": "loopback" } +``` + +The Swift app reads `~/.ccs/bar.json` to find the server. + +## Loopback / Localhost Requirement + +CCS Bar talks only to `http://127.0.0.1:`. v1 supports `authMode: "loopback"` only, meaning dashboard auth disabled on localhost. + +If you bind the dashboard beyond localhost (for example `--host` set to a non-loopback address) with dashboard auth disabled, the bar's read endpoints (`GET /api/bar/summary`, `GET /api/bar/analytics`) are refused for non-loopback callers, and the app cannot reach the server. Keep the dashboard on loopback for CCS Bar to work. + +## Uninstall + +```bash +ccs bar uninstall +``` + +This removes `~/Applications/CCS Bar.app` and the installed version pin. It is a no-op if the app is not present. + +## Troubleshooting + +- Server failed to start: usually a port conflict. Free the port or re-run `ccs bar` to pick a fresh one. +- App won't open (Gatekeeper): right-click and Open, or clear quarantine with the `xattr` command above. +- Blank app: the web-server is not running. Re-run `ccs bar` and confirm `~/.ccs/bar.json` exists. +- Quota not updating: re-open the menu to force a refresh, or confirm the server is still reachable on loopback. + +## Development + +The source lives in `macos-bar/`. Contributors can build and run the logic checks with a Swift 5.9+ toolchain (CommandLineTools is enough, full Xcode not required): + +```bash +swift build # build all targets, including the app +swift run ccs-bar-check # run the logic tests +``` diff --git a/ui/src/components/profiles/ccs-bar-banner.tsx b/ui/src/components/profiles/ccs-bar-banner.tsx new file mode 100644 index 00000000..d8fb9f3f --- /dev/null +++ b/ui/src/components/profiles/ccs-bar-banner.tsx @@ -0,0 +1,101 @@ +/** + * CCS Bar Feature Banner + * Dismissible announcement banner promoting the native macOS menu-bar app. + * + * Rendered only on macOS: the CTA is an install action (`ccs bar install`) that + * has no effect on other platforms, so showing it elsewhere would be misleading. + */ + +/* eslint-disable react-hooks/set-state-in-effect */ +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { X, MonitorDot, ExternalLink } from 'lucide-react'; +import { Button } from '@/components/ui/button'; + +const BANNER_DISMISSED_KEY = 'ccs:ccs-bar-banner-dismissed'; + +// User-facing docs page for CCS Bar (flat Markdown in the ccs/cli docs tree). +const CCS_BAR_DOCS_URL = 'https://github.com/kaitranntt/ccs/blob/main/docs/ccs-bar.md'; + +// Lightweight, dependency-free macOS detection. Kept inline because no other +// component needs platform detection; a shared hook would be premature. +const isMacOS = + typeof navigator !== 'undefined' && + /Mac|iPhone|iPad/i.test(navigator.userAgent || navigator.platform || ''); + +interface CcsBarBannerProps { + onInstallClick?: () => void; +} + +export function CcsBarBanner({ onInstallClick }: CcsBarBannerProps) { + const { t } = useTranslation(); + const [dismissed, setDismissed] = useState(true); // Start hidden to avoid flash + + // Check localStorage on mount + useEffect(() => { + const isDismissed = localStorage.getItem(BANNER_DISMISSED_KEY) === 'true'; + setDismissed(isDismissed); + }, []); + + const handleDismiss = () => { + localStorage.setItem(BANNER_DISMISSED_KEY, 'true'); + setDismissed(true); + }; + + if (!isMacOS) return null; + if (dismissed) return null; + + return ( +
+
+
+
+ +
+
+

+ {t('ccsBarBanner.new')}: {t('ccsBarBanner.title')} +

+

+ {t('ccsBarBanner.description')}{' '} + + ccs bar install + +

+
+
+ +
+ {onInstallClick && ( + + )} + + Learn more + + + +
+
+
+ ); +} diff --git a/ui/src/components/profiles/ccs-bar-promo-card.tsx b/ui/src/components/profiles/ccs-bar-promo-card.tsx new file mode 100644 index 00000000..70869390 --- /dev/null +++ b/ui/src/components/profiles/ccs-bar-promo-card.tsx @@ -0,0 +1,53 @@ +/** + * CCS Bar Promo Card + * Permanent promotional card for the native macOS menu-bar app, shown in the + * providers sidebar footer. + * + * Rendered only on macOS: the install CTA has no effect elsewhere. + */ + +import { useTranslation } from 'react-i18next'; +import { Button } from '@/components/ui/button'; +import { MonitorDot } from 'lucide-react'; + +// Lightweight, dependency-free macOS detection (see ccs-bar-banner.tsx). +const isMacOS = + typeof navigator !== 'undefined' && + /Mac|iPhone|iPad/i.test(navigator.userAgent || navigator.platform || ''); + +interface CcsBarPromoCardProps { + onInstallClick: () => void; +} + +export function CcsBarPromoCard({ onInstallClick }: CcsBarPromoCardProps) { + const { t } = useTranslation(); + + if (!isMacOS) return null; + + return ( +
+
+
+ +
+
+

+ {t('ccsBarPromo.title')} +

+

+ {t('ccsBarPromo.description')} +

+
+ +
+
+ ); +} diff --git a/ui/src/components/profiles/index.ts b/ui/src/components/profiles/index.ts index d3724660..6a05ba14 100644 --- a/ui/src/components/profiles/index.ts +++ b/ui/src/components/profiles/index.ts @@ -20,5 +20,10 @@ export { OpenRouterModelPicker } from './openrouter-model-picker'; export { OpenRouterPromoCard } from './openrouter-promo-card'; export { OpenRouterQuickStart } from './openrouter-quick-start'; export { AlibabaCodingPlanPromoCard } from './alibaba-coding-plan-promo-card'; + +// CCS Bar (native macOS menu-bar app) promo components +export { CcsBarBanner } from './ccs-bar-banner'; +export { CcsBarPromoCard } from './ccs-bar-promo-card'; + export { ModelTierMapping } from './model-tier-mapping'; export type { TierMapping } from './model-tier-mapping'; diff --git a/ui/src/lib/i18n.ts b/ui/src/lib/i18n.ts index 431281ee..343a6542 100644 --- a/ui/src/lib/i18n.ts +++ b/ui/src/lib/i18n.ts @@ -2475,6 +2475,17 @@ const resources = { title: 'OpenRouter', description: 'Access hundreds of models from one API endpoint.', }, + ccsBarBanner: { + new: 'NEW', + title: 'CCS Bar for macOS', + description: 'See live subscription quota and usage from your menu bar. Install with', + install: 'Install', + }, + ccsBarPromo: { + title: 'CCS Bar (macOS)', + description: 'Live quota and usage in your menu bar.', + install: 'Install', + }, profileCard: { profile: 'Profile', openRouter: 'OpenRouter profile', diff --git a/ui/src/pages/api.tsx b/ui/src/pages/api.tsx index 532126ad..0f44ff7c 100644 --- a/ui/src/pages/api.tsx +++ b/ui/src/pages/api.tsx @@ -22,6 +22,8 @@ import { OpenRouterBanner } from '@/components/profiles/openrouter-banner'; import { OpenRouterQuickStart } from '@/components/profiles/openrouter-quick-start'; import { OpenRouterPromoCard } from '@/components/profiles/openrouter-promo-card'; import { AlibabaCodingPlanPromoCard } from '@/components/profiles/alibaba-coding-plan-promo-card'; +import { CcsBarBanner } from '@/components/profiles/ccs-bar-banner'; +import { CcsBarPromoCard } from '@/components/profiles/ccs-bar-promo-card'; import { useProfiles, useDeleteProfile, @@ -41,6 +43,15 @@ import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { useNavigate } from 'react-router-dom'; +// CCS Bar is installed via the `ccs bar install` CLI command, not from the +// dashboard. The promo CTA therefore opens the user-facing docs page where the +// install/launch steps live, rather than triggering an in-app action. +const CCS_BAR_DOCS_URL = 'https://github.com/kaitranntt/ccs/blob/main/docs/ccs-bar.md'; + +function openCcsBarDocs() { + window.open(CCS_BAR_DOCS_URL, '_blank', 'noopener,noreferrer'); +} + export function ApiPage() { const { t } = useTranslation(); const navigate = useNavigate(); @@ -213,6 +224,7 @@ export function ApiPage() { return (
setCreateDialogOpen(true)} /> + openCcsBarDocs()} />
@@ -363,6 +375,7 @@ export function ApiPage() { setCreateDialogOpen(true); }} /> + openCcsBarDocs()} />