diff --git a/src/brands/alibaba.ts b/src/brands/alibaba.ts new file mode 100644 index 0000000..3da9883 --- /dev/null +++ b/src/brands/alibaba.ts @@ -0,0 +1,178 @@ +import type { TweakccConfig, Theme } from './types.js'; +import { DEFAULT_THEMES } from './defaultThemes.js'; +import { buildBrandMiscConfig } from './miscDefaults.js'; +import { buildDiffPalette } from './diffPalette.js'; +import { formatUserMessage, getUserLabel } from './userLabel.js'; + +type Rgb = { r: number; g: number; b: number }; + +const clamp = (value: number) => Math.max(0, Math.min(255, Math.round(value))); + +const hexToRgb = (hex: string): Rgb => { + const normalized = hex.replace('#', '').trim(); + if (normalized.length === 3) { + const [r, g, b] = normalized.split(''); + return { + r: clamp(parseInt(r + r, 16)), + g: clamp(parseInt(g + g, 16)), + b: clamp(parseInt(b + b, 16)), + }; + } + if (normalized.length !== 6) { + throw new Error(`Unsupported hex color: ${hex}`); + } + return { + r: clamp(parseInt(normalized.slice(0, 2), 16)), + g: clamp(parseInt(normalized.slice(2, 4), 16)), + b: clamp(parseInt(normalized.slice(4, 6), 16)), + }; +}; + +const rgb = (hex: string) => { + const { r, g, b } = hexToRgb(hex); + return `rgb(${r},${g},${b})`; +}; + +const mix = (hexA: string, hexB: string, weight: number) => { + const a = hexToRgb(hexA); + const b = hexToRgb(hexB); + const w = Math.max(0, Math.min(1, weight)); + return `rgb(${clamp(a.r + (b.r - a.r) * w)},${clamp(a.g + (b.g - a.g) * w)},${clamp(a.b + (b.b - a.b) * w)})`; +}; + +const lighten = (hex: string, weight: number) => mix(hex, '#ffffff', weight); + +// Alibaba Cloud palette: purple, dark blue, aqua, white +const palette = { + base: '#0d1117', + surface: '#161b22', + panel: '#21262d', + border: '#30363d', + borderStrong: '#484f58', + text: '#f0f6fc', + textMuted: '#8b949e', + textDim: '#6e7681', + purple: '#a371f7', + purpleSoft: '#c9a8ff', + purpleDeep: '#7c3aed', + darkBlue: '#1f6feb', + darkBlueSoft: '#58a6ff', + darkBlueDeep: '#0d419d', + aqua: '#2dd4bf', + aquaSoft: '#5eead4', + aquaDeep: '#14b8a6', + white: '#ffffff', + green: '#3fb950', + red: '#f85149', + orange: '#d29922', +}; + +const theme: Theme = { + name: 'Alibaba Aurora', + id: 'alibaba', + colors: { + autoAccept: rgb(palette.green), + bashBorder: rgb(palette.aqua), + claude: rgb(palette.purple), + claudeShimmer: rgb(palette.purpleSoft), + claudeBlue_FOR_SYSTEM_SPINNER: rgb(palette.darkBlue), + claudeBlueShimmer_FOR_SYSTEM_SPINNER: rgb(palette.darkBlueSoft), + permission: rgb(palette.darkBlue), + permissionShimmer: rgb(palette.darkBlueSoft), + planMode: rgb(palette.green), + ide: rgb(palette.darkBlueSoft), + promptBorder: rgb(palette.border), + promptBorderShimmer: rgb(palette.borderStrong), + text: rgb(palette.text), + inverseText: rgb(palette.base), + inactive: rgb(palette.textDim), + subtle: rgb(palette.border), + suggestion: rgb(palette.darkBlueSoft), + remember: rgb(palette.purple), + background: rgb(palette.base), + success: rgb(palette.green), + error: rgb(palette.red), + warning: rgb(palette.orange), + warningShimmer: rgb(palette.aquaSoft), + ...buildDiffPalette(), + red_FOR_SUBAGENTS_ONLY: rgb(palette.red), + blue_FOR_SUBAGENTS_ONLY: rgb(palette.darkBlueDeep), + green_FOR_SUBAGENTS_ONLY: rgb(palette.green), + yellow_FOR_SUBAGENTS_ONLY: rgb(palette.orange), + purple_FOR_SUBAGENTS_ONLY: rgb(palette.purple), + orange_FOR_SUBAGENTS_ONLY: rgb(palette.orange), + pink_FOR_SUBAGENTS_ONLY: rgb(palette.purpleSoft), + cyan_FOR_SUBAGENTS_ONLY: rgb(palette.aqua), + professionalBlue: rgb(palette.darkBlueSoft), + rainbow_red: rgb(palette.red), + rainbow_orange: rgb(palette.orange), + rainbow_yellow: rgb(palette.aqua), + rainbow_green: rgb(palette.green), + rainbow_blue: rgb(palette.darkBlue), + rainbow_indigo: rgb(palette.purpleDeep), + rainbow_violet: rgb(palette.purple), + rainbow_red_shimmer: lighten(palette.red, 0.35), + rainbow_orange_shimmer: lighten(palette.orange, 0.35), + rainbow_yellow_shimmer: lighten(palette.aqua, 0.25), + rainbow_green_shimmer: lighten(palette.green, 0.35), + rainbow_blue_shimmer: lighten(palette.darkBlue, 0.35), + rainbow_indigo_shimmer: lighten(palette.purpleDeep, 0.35), + rainbow_violet_shimmer: lighten(palette.purple, 0.35), + clawd_body: rgb(palette.purple), + clawd_background: rgb(palette.base), + userMessageBackground: mix(palette.panel, palette.purple, 0.15), + bashMessageBackgroundColor: mix(palette.panel, palette.aqua, 0.08), + memoryBackgroundColor: mix(palette.panel, palette.darkBlue, 0.12), + rate_limit_fill: rgb(palette.purple), + rate_limit_empty: rgb(palette.borderStrong), + }, +}; + +export const buildAlibabaTweakccConfig = (): TweakccConfig => ({ + ccVersion: '', + ccInstallationPath: null, + lastModified: new Date().toISOString(), + changesApplied: false, + hidePiebaldAnnouncement: true, + settings: { + themes: [theme, ...DEFAULT_THEMES.filter((t) => t.id !== theme.id)], + thinkingVerbs: { + format: '{}... ', + verbs: [ + 'Computing', + 'Processing', + 'Analyzing', + 'Optimizing', + 'Routing', + 'Mapping', + 'Synthesizing', + 'Compiling', + 'Refining', + 'Validating', + 'Aligning', + 'Delivering', + ], + }, + thinkingStyle: { + updateInterval: 120, + phases: ['·', '•', '◦', '•'], + reverseMirror: false, + }, + userMessageDisplay: { + format: formatUserMessage(getUserLabel()), + styling: ['bold'], + foregroundColor: 'default', + backgroundColor: 'default', + borderStyle: 'topBottomBold', + borderColor: rgb(palette.purple), + paddingX: 1, + paddingY: 0, + fitBoxToContent: true, + }, + inputBox: { + removeBorder: true, + }, + misc: buildBrandMiscConfig(), + claudeMdAltNames: null, + }, +}); diff --git a/src/brands/index.ts b/src/brands/index.ts index 13e304b..3005a7a 100644 --- a/src/brands/index.ts +++ b/src/brands/index.ts @@ -9,6 +9,7 @@ import { buildOllamaTweakccConfig } from './ollama.js'; import { buildGatewayzTweakccConfig } from './gatewayz.js'; import { buildVercelTweakccConfig } from './vercel.js'; import { buildNanoGPTTweakccConfig } from './nanogpt.js'; +import { buildAlibabaTweakccConfig } from './alibaba.js'; export interface BrandPreset { key: string; @@ -78,6 +79,12 @@ const BRAND_PRESETS: Record = { description: 'Reflective silver/chrome theme for pure Claude Code experience.', buildTweakccConfig: buildMirrorTweakccConfig, }, + alibaba: { + key: 'alibaba', + label: 'Alibaba Aurora', + description: 'Purple, dark blue, and aqua palette for Alibaba Cloud Coding Plan.', + buildTweakccConfig: buildAlibabaTweakccConfig, + }, }; export const listBrandPresets = (): BrandPreset[] => Object.values(BRAND_PRESETS); diff --git a/src/cli/help.ts b/src/cli/help.ts index 71ef524..c0890a3 100644 --- a/src/cli/help.ts +++ b/src/cli/help.ts @@ -19,6 +19,7 @@ FOCUS QUICK START npx cc-mirror quick --provider mirror # Fastest path to Claude npx cc-mirror quick --provider zai # Z.ai with GLM models + npx cc-mirror quick --provider alibaba # Alibaba Cloud Coding Plan npx cc-mirror quick --provider ollama # Ollama local + cloud models npx cc-mirror # Interactive TUI @@ -34,10 +35,10 @@ COMMANDS OPTIONS (create/quick) --name Variant name (becomes CLI command) - --provider Provider: kimi | minimax | zai | openrouter | vercel | ollama | nanogpt | ccrouter | mirror | gatewayz + --provider Provider: kimi | minimax | zai | alibaba | openrouter | vercel | ollama | nanogpt | ccrouter | mirror | gatewayz --api-key Provider API key --auth-token Alias for --api-key (auth-token providers) - --brand Theme: auto | none | kimi | minimax | zai | openrouter | vercel | ollama | nanogpt | ccrouter | mirror | gatewayz + --brand Theme: auto | none | kimi | minimax | zai | alibaba | openrouter | vercel | ollama | nanogpt | ccrouter | mirror | gatewayz --tui / --no-tui Force TUI on/off OPTIONS (advanced) @@ -62,6 +63,7 @@ PROVIDERS kimi kimi-for-coding via Kimi Code minimax MiniMax via MiniMax Cloud zai GLM-5/4.7/4.5-Air via Z.ai Coding Plan + alibaba Alibaba Cloud Coding Plan (DashScope) openrouter 100+ models via OpenRouter vercel Vercel AI Gateway ollama Local + cloud models via Ollama @@ -72,6 +74,7 @@ PROVIDERS EXAMPLES npx cc-mirror quick --provider mirror --name mclaude npx cc-mirror quick --provider zai --api-key "$Z_AI_API_KEY" + npx cc-mirror quick --provider alibaba --api-key "$ALIBABA_API_KEY" npx cc-mirror apply mclaude npx cc-mirror update mclaude --claude-version latest npx cc-mirror doctor diff --git a/src/core/wrapper.ts b/src/core/wrapper.ts index 48f496b..d074347 100644 --- a/src/core/wrapper.ts +++ b/src/core/wrapper.ts @@ -59,6 +59,11 @@ const C = { olSecondary: '\x1b[38;5;223m', // Light tan olAccent: '\x1b[38;5;137m', // Deep brown olDim: '\x1b[38;5;101m', // Muted brown + // Alibaba: Purple/Dark Blue/Aqua gradient (brand: #a371f7, #1f6feb, #2dd4bf) + alPrimary: '\x1b[38;5;141m', // Purple + alSecondary: '\x1b[38;5;33m', // Dark blue + alAccent: '\x1b[38;5;50m', // Aqua + alDim: '\x1b[38;5;97m', // Muted purple // Default: White/Gray defPrimary: '\x1b[38;5;255m', // White defDim: '\x1b[38;5;245m', // Gray @@ -228,6 +233,24 @@ const SPLASH_ART: SplashArt = { `${C.olSecondary} Run Models Locally${C.reset}`, '', ], + // Alibaba: Cloud + block letters + alibaba: [ + '', + `${C.alDim} ◈━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━◈${C.reset}`, + `${C.alSecondary} A L I B A B A C L O U D${C.reset}`, + `${C.alDim} ◈━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━◈${C.reset}`, + '', + `${C.alPrimary} ██████╗ ██████╗ █████╗ ██╗ ██╗███████╗██████╗ ${C.alAccent}███████╗███████╗${C.reset}`, + `${C.alPrimary} ██╔════╝ ██╔═══██╗██╔══██╗╚██╗██╔╝██╔════╝██╔══██╗${C.alAccent}╚══███╔╝╚══███╔╝${C.reset}`, + `${C.alSecondary} ██║ ███╗██║ ██║███████║ ╚███╔╝ █████╗ ██████╔╝${C.alAccent} ███╔╝ ███╔╝${C.reset}`, + `${C.alSecondary} ██║ ██║██║ ██║██╔══██║ ██╔██╗ ██╔══╝ ██╔══██╗${C.alAccent} ███╔╝ ███╔╝${C.reset}`, + `${C.alAccent} ╚██████╔╝╚██████╔╝██║ ██║██╔╝ ██╗███████╗██║ ██║${C.alAccent}███████╗███████╗${C.reset}`, + `${C.alAccent} ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝${C.alAccent}╚══════╝╚══════╝${C.reset}`, + '', + `${C.alDim} ━━━━━━━━━━━━━━━━━━━━━━━${C.alPrimary}◆${C.alDim}━━━━━━━━━━━━━━━━━━━━━━━${C.reset}`, + `${C.alSecondary} Coding Plan ${C.alDim}━${C.alSecondary} DashScope${C.reset}`, + '', + ], default: [ '', `${C.defPrimary} ██████╗ ██████╗ ${C.defDim}━━ M I R R O R${C.reset}`, @@ -244,6 +267,7 @@ const KNOWN_SPLASH_STYLES = [ 'zai', 'minimax', 'kimi', + 'alibaba', 'openrouter', 'ccrouter', 'mirror', @@ -489,6 +513,12 @@ export const writeWrapper = ( 'CCMOL', ' __cc_show_label="0"', ' ;;', + ' alibaba)', + " cat <<'CCMAL'", + ...SPLASH_ART.alibaba, + 'CCMAL', + ' __cc_show_label="0"', + ' ;;', ' *)', " cat <<'CCMGEN'", ...SPLASH_ART.default, diff --git a/src/providers/index.ts b/src/providers/index.ts index e34026e..c6b73db 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -43,6 +43,7 @@ export const PROVIDER_DISPLAY_ORDER = [ 'kimi', 'minimax', 'zai', + 'alibaba', 'openrouter', 'vercel', 'ollama', @@ -89,6 +90,22 @@ const PROVIDERS: Record = { }, apiKeyLabel: 'Zai API key', }, + alibaba: { + key: 'alibaba', + label: 'Alibaba Cloud', + description: 'Alibaba Cloud Coding Plan (DashScope Anthropic-compatible)', + baseUrl: 'https://coding-intl.dashscope.aliyuncs.com/apps/anthropic', + env: { + API_TIMEOUT_MS: DEFAULT_TIMEOUT_MS, + ANTHROPIC_DEFAULT_HAIKU_MODEL: 'qwen3.5-plus', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'kimi-k2.5', + ANTHROPIC_DEFAULT_OPUS_MODEL: 'glm-5', + CC_MIRROR_SPLASH: 1, + CC_MIRROR_PROVIDER_LABEL: 'Alibaba Cloud', + CC_MIRROR_SPLASH_STYLE: 'alibaba', + }, + apiKeyLabel: 'Alibaba Cloud API key', + }, minimax: { key: 'minimax', label: 'MiniMax Cloud',