From bd3be23355f48269d5ce74dbf2b5aaf0eda8cf22 Mon Sep 17 00:00:00 2001 From: Huynh Duc Dung Date: Thu, 22 Jan 2026 16:59:23 +0800 Subject: [PATCH 01/11] feat: add Ollama provider support Add support for Ollama as a provider in CCS, enabling users to use local Ollama models (qwen3-coder, gpt-oss:20b, gpt-oss:120b) and Ollama Cloud models (glm-4.7:cloud, qwen3-coder:480b). Changes: - Add base-ollama.settings.json for local Ollama (localhost:11434) - Add base-ollama-cloud.settings.json for ollama.com cloud API - Add 'ollama' and 'ollama-cloud' presets to provider-presets.ts - Update help documentation in api-command.ts and help-command.ts Usage: ccs api create --preset ollama # Local Ollama ccs api create --preset ollama-cloud # Ollama Cloud Ref: https://github.com/kaitranntt/ccs/issues/353 Co-Authored-By: Claude --- config/base-ollama-cloud.settings.json | 10 ++++++++++ config/base-ollama.settings.json | 10 ++++++++++ src/api/services/provider-presets.ts | 22 ++++++++++++++++++++++ src/commands/api-command.ts | 8 +++++++- src/commands/help-command.ts | 2 ++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 config/base-ollama-cloud.settings.json create mode 100644 config/base-ollama.settings.json diff --git a/config/base-ollama-cloud.settings.json b/config/base-ollama-cloud.settings.json new file mode 100644 index 00000000..a574e88b --- /dev/null +++ b/config/base-ollama-cloud.settings.json @@ -0,0 +1,10 @@ +{ + "env": { + "ANTHROPIC_BASE_URL": "https://ollama.com", + "ANTHROPIC_AUTH_TOKEN": "YOUR_OLLAMA_CLOUD_API_KEY_HERE", + "ANTHROPIC_MODEL": "glm-4.7:cloud", + "ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3-coder:480b", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.7:cloud", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "minimax-m2.1:cloud" + } +} diff --git a/config/base-ollama.settings.json b/config/base-ollama.settings.json new file mode 100644 index 00000000..8c14903e --- /dev/null +++ b/config/base-ollama.settings.json @@ -0,0 +1,10 @@ +{ + "env": { + "ANTHROPIC_BASE_URL": "http://localhost:11434", + "ANTHROPIC_AUTH_TOKEN": "ollama", + "ANTHROPIC_MODEL": "qwen3-coder", + "ANTHROPIC_DEFAULT_OPUS_MODEL": "gpt-oss:120b", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-oss:20b", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "qwen3-coder" + } +} diff --git a/src/api/services/provider-presets.ts b/src/api/services/provider-presets.ts index 82223565..7a70a61a 100644 --- a/src/api/services/provider-presets.ts +++ b/src/api/services/provider-presets.ts @@ -43,6 +43,17 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyHint: 'Get your API key at openrouter.ai/keys', category: 'recommended', }, + { + id: 'ollama', + name: 'Ollama (Local)', + description: 'Local open-source models via Ollama (32K+ context recommended)', + baseUrl: 'http://localhost:11434', + defaultProfileName: 'ollama', + defaultModel: 'qwen3-coder', + apiKeyPlaceholder: 'ollama', + apiKeyHint: 'Install Ollama from ollama.com - no API key needed for local', + category: 'recommended', + }, // Alternative providers { id: 'glm', @@ -131,6 +142,17 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio', category: 'alternative', }, + { + id: 'ollama-cloud', + name: 'Ollama Cloud', + description: 'Ollama.com cloud models (glm-4.7:cloud, qwen3-coder:480b)', + baseUrl: 'https://ollama.com', + defaultProfileName: 'ollama-cloud', + defaultModel: 'glm-4.7:cloud', + apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY', + apiKeyHint: 'Get your API key at ollama.com', + category: 'alternative', + }, ]; /** Get preset by ID */ diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index 1c51b9fa..a96707a0 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -426,7 +426,7 @@ async function showHelp(): Promise { console.log(''); console.log(subheader('Options')); console.log( - ` ${color('--preset ', 'command')} Use provider preset (openrouter, glm, glmt, kimi, foundry, mm, deepseek, qwen)` + ` ${color('--preset ', 'command')} Use provider preset (openrouter, ollama, glm, glmt, kimi, foundry, mm, deepseek, qwen)` ); console.log(` ${color('--base-url ', 'command')} API base URL (create)`); console.log(` ${color('--api-key ', 'command')} API key (create)`); @@ -438,6 +438,12 @@ async function showHelp(): Promise { console.log( ` ${color('openrouter', 'command')} OpenRouter - 349+ models (Claude, GPT, Gemini, Llama)` ); + console.log( + ` ${color('ollama', 'command')} Ollama - Local open-source models (no API key)` + ); + console.log( + ` ${color('ollama-cloud', 'command')} Ollama Cloud - glm-4.7:cloud, qwen3-coder:480b` + ); console.log(` ${color('glm', 'command')} GLM - Claude via Z.AI`); console.log(` ${color('glmt', 'command')} GLMT - GLM with Thinking mode`); console.log(` ${color('kimi', 'command')} Kimi - Moonshot AI reasoning model`); diff --git a/src/commands/help-command.ts b/src/commands/help-command.ts index effb55a4..3d7540f4 100644 --- a/src/commands/help-command.ts +++ b/src/commands/help-command.ts @@ -127,6 +127,8 @@ Run ${color('ccs config', 'command')} for web dashboard`.trim(); ['ccs glm', 'GLM 4.6 (API key required)'], ['ccs glmt', 'GLM with thinking mode'], ['ccs kimi', 'Kimi for Coding (API key)'], + ['ccs ollama', 'Local Ollama (http://localhost:11434)'], + ['ccs ollama-cloud', 'Ollama Cloud (API key required)'], ['', ''], // Spacer ['ccs api create', 'Create custom API profile'], ['ccs api remove', 'Remove an API profile'], From c9604be5e1089c38d10047f65ab531a25ea14fc5 Mon Sep 17 00:00:00 2001 From: Huynh Duc Dung Date: Thu, 22 Jan 2026 17:04:36 +0800 Subject: [PATCH 02/11] docs: add Ollama to Built-in Providers table and usage examples Update README.md to include: - Ollama and Ollama Cloud in the Built-in Providers table - Ollama integration note with setup instructions - ccs ollama in Basic Commands section - Ollama example in Parallel Workflows Ref: https://github.com/kaitranntt/ccs/pull/354 Co-Authored-By: Claude --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a0382846..31d4cc0b 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,8 @@ The dashboard provides visual management for all account types: | **Kiro** | OAuth | `ccs kiro` | AWS CodeWhisperer (Claude-powered) | | **Antigravity** | OAuth | `ccs agy` | Alternative routing | | **OpenRouter** | API Key | `ccs openrouter` | 300+ models, unified API | +| **Ollama** | Local | `ccs ollama` | Local open-source models, privacy | +| **Ollama Cloud** | API Key | `ccs ollama-cloud` | Cloud-hosted open-source models | | **GLM** | API Key | `ccs glm` | Cost-optimized execution | | **Kimi** | API Key | `ccs kimi` | Long-context, thinking mode | | **Azure Foundry** | API Key | `ccs foundry` | Claude via Microsoft Azure | @@ -104,6 +106,8 @@ The dashboard provides visual management for all account types: **OpenRouter Integration** (v7.0.0): CCS v7.0.0 adds OpenRouter with interactive model picker, dynamic discovery, and tier mapping (opus/sonnet/haiku). Create via `ccs api create --preset openrouter` or dashboard. +**Ollama Integration**: Run local open-source models (qwen3-coder, gpt-oss:20b) with full privacy. Use `ccs api create --preset ollama` - requires [Ollama v0.14.0+](https://ollama.com) installed. For cloud models, use `ccs api create --preset ollama-cloud`. + **Azure Foundry**: Use `ccs api create --preset foundry` to set up Claude via Microsoft Azure AI Foundry. Requires Azure resource and API key from [ai.azure.com](https://ai.azure.com). ![OpenRouter API Profiles](assets/screenshots/api-profiles-openrouter.webp) @@ -131,6 +135,7 @@ ccs codex # OpenAI Codex (OAuth) ccs kiro # Kiro/AWS CodeWhisperer (OAuth) ccs ghcp # GitHub Copilot (OAuth device flow) ccs agy # Antigravity (OAuth) +ccs ollama # Local Ollama (no API key needed) ccs glm # GLM (API key) ``` @@ -145,7 +150,10 @@ ccs work "design the authentication system" # Terminal 2: Execution (GLM - cost optimized) ccs glm "implement the user service from the plan" -# Terminal 3: Review (Gemini) +# Terminal 3: Local testing (Ollama - offline, privacy) +ccs ollama "run tests and generate coverage report" + +# Terminal 4: Review (Gemini) ccs gemini "review the implementation for security issues" ``` From ef2c8bba12e9ab164fa5b4be4c8fbb60617a20a1 Mon Sep 17 00:00:00 2001 From: Huynh Duc Dung Date: Thu, 22 Jan 2026 17:21:50 +0800 Subject: [PATCH 03/11] fix(api): skip API key prompt for local Ollama preset Local Ollama doesn't require an API key, but the CLI was still prompting for one. This adds a `requiresApiKey` property to provider presets to control whether the API key step is required. - Add `requiresApiKey` property to ProviderPreset interface - Set `requiresApiKey: false` for local Ollama (no key needed) - Set `requiresApiKey: true` for OpenRouter and Ollama Cloud - Skip API key prompt and show info message when not required Co-Authored-By: Claude --- src/api/services/provider-presets.ts | 5 +++++ src/commands/api-command.ts | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/api/services/provider-presets.ts b/src/api/services/provider-presets.ts index 7a70a61a..fd986a4d 100644 --- a/src/api/services/provider-presets.ts +++ b/src/api/services/provider-presets.ts @@ -17,6 +17,8 @@ export interface ProviderPreset { apiKeyPlaceholder: string; apiKeyHint: string; category: PresetCategory; + /** Whether API key is required (false for local providers like Ollama) */ + requiresApiKey?: boolean; /** Additional env vars for thinking mode, etc. */ extraEnv?: Record; /** Enable always thinking mode */ @@ -42,6 +44,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'sk-or-...', apiKeyHint: 'Get your API key at openrouter.ai/keys', category: 'recommended', + requiresApiKey: true, }, { id: 'ollama', @@ -53,6 +56,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'ollama', apiKeyHint: 'Install Ollama from ollama.com - no API key needed for local', category: 'recommended', + requiresApiKey: false, }, // Alternative providers { @@ -152,6 +156,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY', apiKeyHint: 'Get your API key at ollama.com', category: 'alternative', + requiresApiKey: true, }, ]; diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index a96707a0..b7934bf7 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -181,9 +181,15 @@ async function handleCreate(args: string[]): Promise { console.log(dim('Note: For OpenRouter, ANTHROPIC_API_KEY should be empty.')); } - // Step 3: API Key + // Step 3: API Key (skip if preset doesn't require one) let apiKey = parsedArgs.apiKey; - if (!apiKey) { + const requiresApiKey = preset?.requiresApiKey !== false; + + if (!requiresApiKey) { + // Preset doesn't require API key (e.g., local Ollama) + console.log(info('No API key required for local Ollama')); + apiKey = ''; // Empty string for local providers + } else if (!apiKey) { const keyPrompt = preset?.apiKeyHint ? `API Key (${preset.apiKeyHint})` : 'API Key'; apiKey = await InteractivePrompt.password(keyPrompt); if (!apiKey) { From dc6977d32e5511b24f6a403f609507e9cd19af19 Mon Sep 17 00:00:00 2001 From: Huynh Duc Dung Date: Thu, 22 Jan 2026 17:29:32 +0800 Subject: [PATCH 04/11] fix(api): skip API key prompt for local Ollama using noApiKey flag Local Ollama doesn't require an API key, but the CLI was still prompting for one. This adds a `noApiKey` flag to provider presets to mark providers that don't need authentication. - Add `noApiKey?: boolean` property to ProviderPreset interface - Set `noApiKey: true` for local Ollama (no key needed) - All other presets unchanged (default to requiring API key) - Skip API key prompt and show info message when noApiKey is true Co-Authored-By: Claude --- src/api/services/provider-presets.ts | 8 +++----- src/commands/api-command.ts | 5 ++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/api/services/provider-presets.ts b/src/api/services/provider-presets.ts index fd986a4d..0a518e3b 100644 --- a/src/api/services/provider-presets.ts +++ b/src/api/services/provider-presets.ts @@ -17,8 +17,8 @@ export interface ProviderPreset { apiKeyPlaceholder: string; apiKeyHint: string; category: PresetCategory; - /** Whether API key is required (false for local providers like Ollama) */ - requiresApiKey?: boolean; + /** Set to true for local providers that don't need API key */ + noApiKey?: boolean; /** Additional env vars for thinking mode, etc. */ extraEnv?: Record; /** Enable always thinking mode */ @@ -44,7 +44,6 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'sk-or-...', apiKeyHint: 'Get your API key at openrouter.ai/keys', category: 'recommended', - requiresApiKey: true, }, { id: 'ollama', @@ -56,7 +55,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'ollama', apiKeyHint: 'Install Ollama from ollama.com - no API key needed for local', category: 'recommended', - requiresApiKey: false, + noApiKey: true, }, // Alternative providers { @@ -156,7 +155,6 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY', apiKeyHint: 'Get your API key at ollama.com', category: 'alternative', - requiresApiKey: true, }, ]; diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index b7934bf7..0327978d 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -181,11 +181,10 @@ async function handleCreate(args: string[]): Promise { console.log(dim('Note: For OpenRouter, ANTHROPIC_API_KEY should be empty.')); } - // Step 3: API Key (skip if preset doesn't require one) + // Step 3: API Key (skip if preset has noApiKey flag) let apiKey = parsedArgs.apiKey; - const requiresApiKey = preset?.requiresApiKey !== false; - if (!requiresApiKey) { + if (preset?.noApiKey) { // Preset doesn't require API key (e.g., local Ollama) console.log(info('No API key required for local Ollama')); apiKey = ''; // Empty string for local providers From 2b7d18c4c6631cb949c82a3eecc83beb0c885319 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 22 Jan 2026 14:09:59 -0500 Subject: [PATCH 05/11] feat(ui): add Ollama provider presets to dashboard - Add ollama (local) and ollama-cloud presets to UI provider-presets.ts - Update profile-create-dialog to handle requiresApiKey: false - Show "No API Key Required" info box for local Ollama - Add --api-key ignored warning to CLI for local Ollama preset - Add ollama-cloud to CLI help text preset list Fixes edge cases identified in PR review: - UI dashboard now has parity with CLI Ollama support - Conditional API key validation in profile creation form --- src/commands/api-command.ts | 5 +- .../profiles/profile-create-dialog.tsx | 84 ++++++++++++------- ui/src/lib/provider-presets.ts | 28 +++++++ 3 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index 0327978d..41706a30 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -186,6 +186,9 @@ async function handleCreate(args: string[]): Promise { if (preset?.noApiKey) { // Preset doesn't require API key (e.g., local Ollama) + if (parsedArgs.apiKey) { + console.log(dim('Note: API key ignored for local Ollama (not required)')); + } console.log(info('No API key required for local Ollama')); apiKey = ''; // Empty string for local providers } else if (!apiKey) { @@ -431,7 +434,7 @@ async function showHelp(): Promise { console.log(''); console.log(subheader('Options')); console.log( - ` ${color('--preset ', 'command')} Use provider preset (openrouter, ollama, glm, glmt, kimi, foundry, mm, deepseek, qwen)` + ` ${color('--preset ', 'command')} Use provider preset (openrouter, ollama, ollama-cloud, glm, glmt, kimi, foundry, mm, deepseek, qwen)` ); console.log(` ${color('--base-url ', 'command')} API base URL (create)`); console.log(` ${color('--api-key ', 'command')} API key (create)`); diff --git a/ui/src/components/profiles/profile-create-dialog.tsx b/ui/src/components/profiles/profile-create-dialog.tsx index 37f92035..0d7fff12 100644 --- a/ui/src/components/profiles/profile-create-dialog.tsx +++ b/ui/src/components/profiles/profile-create-dialog.tsx @@ -47,7 +47,7 @@ const schema = z.object({ .min(1, 'Name is required') .regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/, 'Must start with letter, only letters/numbers/.-_'), baseUrl: z.string().url('Invalid URL format'), - apiKey: z.string().min(1, 'API key is required'), + apiKey: z.string(), // Validation handled conditionally in onSubmit model: z.string().optional(), opusModel: z.string().optional(), sonnetModel: z.string().optional(), @@ -207,9 +207,16 @@ export function ProfileCreateDialog({ }, [baseUrlValue, selectedPreset]); const onSubmit = async (data: FormData) => { + // Validate API key - required unless preset has requiresApiKey: false + if (currentPreset?.requiresApiKey !== false && !data.apiKey) { + toast.error('API key is required'); + return; + } // Use user-provided baseUrl (allows customization of preset URLs) const finalData = { ...data, + // For presets that don't require API key, use empty string + apiKey: currentPreset?.requiresApiKey === false ? '' : data.apiKey, }; try { await createMutation.mutateAsync(finalData); @@ -367,38 +374,51 @@ export function ProfileCreateDialog({ )} - {/* API Key */} -
- -
- - + {/* API Key - hidden for presets that don't require it */} + {currentPreset?.requiresApiKey !== false ? ( +
+ +
+ + +
+ {errors.apiKey ? ( +

{errors.apiKey.message}

+ ) : ( + currentPreset?.apiKeyHint && ( +

{currentPreset.apiKeyHint}

+ ) + )}
- {errors.apiKey ? ( -

{errors.apiKey.message}

- ) : ( - currentPreset?.apiKeyHint && ( -

{currentPreset.apiKeyHint}

- ) - )} -
+ ) : ( +
+ +
+

No API Key Required

+

+ {currentPreset?.apiKeyHint || + 'This provider runs locally and does not require authentication.'} +

+
+
+ )} diff --git a/ui/src/lib/provider-presets.ts b/ui/src/lib/provider-presets.ts index 591260cb..2cd68f18 100644 --- a/ui/src/lib/provider-presets.ts +++ b/ui/src/lib/provider-presets.ts @@ -44,6 +44,21 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyHint: 'Get your API key at openrouter.ai/keys', category: 'recommended', }, + // Recommended - Ollama (Local) + { + id: 'ollama', + name: 'Ollama (Local)', + description: 'Local open-source models via Ollama (32K+ context)', + baseUrl: 'http://localhost:11434', + defaultProfileName: 'ollama', + badge: 'Local', + featured: true, + defaultModel: 'qwen3-coder', + requiresApiKey: false, + apiKeyPlaceholder: '', + apiKeyHint: 'No API key required for local Ollama', + category: 'recommended', + }, // Alternative providers - GLM/GLMT/Kimi { id: 'glm', @@ -136,6 +151,19 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio', category: 'alternative', }, + { + id: 'ollama-cloud', + name: 'Ollama Cloud', + description: 'Ollama.com cloud models (glm-4.7:cloud, qwen3-coder:480b)', + baseUrl: 'https://ollama.com', + defaultProfileName: 'ollama-cloud', + badge: 'Cloud', + defaultModel: 'glm-4.7:cloud', + requiresApiKey: true, + apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY', + apiKeyHint: 'Get your API key at ollama.com', + category: 'alternative', + }, ]; /** Get presets by category */ From 2cb77f2dfd8376905e76cdd951830d24f2d29bbf Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 22 Jan 2026 14:24:37 -0500 Subject: [PATCH 06/11] feat(ui): add Ollama logo and make API key optional - Add Ollama SVG logo to ui/public/icons/ - Add icon property to both ollama and ollama-cloud presets - Make API key field optional for local Ollama (show input with hint) - Update placeholder: "Optional - only if auth is enabled" - Allows power users with Ollama auth configured to provide key --- ui/public/icons/ollama.svg | 1 + .../profiles/profile-create-dialog.tsx | 91 ++++++++++--------- ui/src/lib/provider-presets.ts | 2 + 3 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 ui/public/icons/ollama.svg diff --git a/ui/public/icons/ollama.svg b/ui/public/icons/ollama.svg new file mode 100644 index 00000000..cc887e3d --- /dev/null +++ b/ui/public/icons/ollama.svg @@ -0,0 +1 @@ +Ollama \ No newline at end of file diff --git a/ui/src/components/profiles/profile-create-dialog.tsx b/ui/src/components/profiles/profile-create-dialog.tsx index 0d7fff12..2c3d283a 100644 --- a/ui/src/components/profiles/profile-create-dialog.tsx +++ b/ui/src/components/profiles/profile-create-dialog.tsx @@ -215,8 +215,8 @@ export function ProfileCreateDialog({ // Use user-provided baseUrl (allows customization of preset URLs) const finalData = { ...data, - // For presets that don't require API key, use empty string - apiKey: currentPreset?.requiresApiKey === false ? '' : data.apiKey, + // Use provided API key, or empty string if not provided (for optional auth providers) + apiKey: data.apiKey || '', }; try { await createMutation.mutateAsync(finalData); @@ -374,51 +374,52 @@ export function ProfileCreateDialog({ )}
- {/* API Key - hidden for presets that don't require it */} - {currentPreset?.requiresApiKey !== false ? ( -
- -
- - -
- {errors.apiKey ? ( -

{errors.apiKey.message}

- ) : ( - currentPreset?.apiKeyHint && ( -

{currentPreset.apiKeyHint}

- ) + {/* API Key - optional for presets that don't require it */} +
+ +
+ +
- ) : ( -
- -
-

No API Key Required

-

- {currentPreset?.apiKeyHint || - 'This provider runs locally and does not require authentication.'} -

-
-
- )} + {errors.apiKey ? ( +

{errors.apiKey.message}

+ ) : currentPreset?.requiresApiKey === false ? ( +

+ Only needed if you have configured Ollama authentication +

+ ) : ( + currentPreset?.apiKeyHint && ( +

{currentPreset.apiKeyHint}

+ ) + )} +
diff --git a/ui/src/lib/provider-presets.ts b/ui/src/lib/provider-presets.ts index 2cd68f18..4b900e4a 100644 --- a/ui/src/lib/provider-presets.ts +++ b/ui/src/lib/provider-presets.ts @@ -53,6 +53,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ defaultProfileName: 'ollama', badge: 'Local', featured: true, + icon: '/icons/ollama.svg', defaultModel: 'qwen3-coder', requiresApiKey: false, apiKeyPlaceholder: '', @@ -158,6 +159,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://ollama.com', defaultProfileName: 'ollama-cloud', badge: 'Cloud', + icon: '/icons/ollama.svg', defaultModel: 'glm-4.7:cloud', requiresApiKey: true, apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY', From 3ce698c5fe3bc013371b51f6c13d01f32611f9a3 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 22 Jan 2026 15:02:49 -0500 Subject: [PATCH 07/11] fix(ollama): align property naming and descriptions - Change CLI noApiKey to requiresApiKey to match UI convention - Set apiKey='ollama' instead of empty string for local Ollama - Align ollama-cloud descriptions between CLI/UI - Keep https://ollama.com endpoint for direct API access --- src/api/services/provider-presets.ts | 10 +++++----- src/commands/api-command.ts | 12 +++++++----- ui/src/lib/provider-presets.ts | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/api/services/provider-presets.ts b/src/api/services/provider-presets.ts index 0a518e3b..d055e4a8 100644 --- a/src/api/services/provider-presets.ts +++ b/src/api/services/provider-presets.ts @@ -17,8 +17,8 @@ export interface ProviderPreset { apiKeyPlaceholder: string; apiKeyHint: string; category: PresetCategory; - /** Set to true for local providers that don't need API key */ - noApiKey?: boolean; + /** Set to false for local providers that don't need API key (default: true) */ + requiresApiKey?: boolean; /** Additional env vars for thinking mode, etc. */ extraEnv?: Record; /** Enable always thinking mode */ @@ -48,14 +48,14 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ { id: 'ollama', name: 'Ollama (Local)', - description: 'Local open-source models via Ollama (32K+ context recommended)', + description: 'Local open-source models via Ollama (32K+ context)', baseUrl: 'http://localhost:11434', defaultProfileName: 'ollama', defaultModel: 'qwen3-coder', apiKeyPlaceholder: 'ollama', apiKeyHint: 'Install Ollama from ollama.com - no API key needed for local', category: 'recommended', - noApiKey: true, + requiresApiKey: false, }, // Alternative providers { @@ -148,7 +148,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ { id: 'ollama-cloud', name: 'Ollama Cloud', - description: 'Ollama.com cloud models (glm-4.7:cloud, qwen3-coder:480b)', + description: 'Ollama cloud models via direct API (glm-4.7:cloud, minimax-m2.1:cloud)', baseUrl: 'https://ollama.com', defaultProfileName: 'ollama-cloud', defaultModel: 'glm-4.7:cloud', diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index 41706a30..2e740ba6 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -181,16 +181,18 @@ async function handleCreate(args: string[]): Promise { console.log(dim('Note: For OpenRouter, ANTHROPIC_API_KEY should be empty.')); } - // Step 3: API Key (skip if preset has noApiKey flag) + // Step 3: API Key (skip if preset has requiresApiKey: false) let apiKey = parsedArgs.apiKey; - if (preset?.noApiKey) { + if (preset?.requiresApiKey === false) { // Preset doesn't require API key (e.g., local Ollama) if (parsedArgs.apiKey) { - console.log(dim('Note: API key ignored for local Ollama (not required)')); + console.log(dim('Note: Using provided API key for local Ollama (optional)')); + apiKey = parsedArgs.apiKey; + } else { + console.log(info('No API key required for local Ollama')); + apiKey = 'ollama'; // Placeholder token matching template } - console.log(info('No API key required for local Ollama')); - apiKey = ''; // Empty string for local providers } else if (!apiKey) { const keyPrompt = preset?.apiKeyHint ? `API Key (${preset.apiKeyHint})` : 'API Key'; apiKey = await InteractivePrompt.password(keyPrompt); diff --git a/ui/src/lib/provider-presets.ts b/ui/src/lib/provider-presets.ts index 4b900e4a..67c3f3ba 100644 --- a/ui/src/lib/provider-presets.ts +++ b/ui/src/lib/provider-presets.ts @@ -155,7 +155,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ { id: 'ollama-cloud', name: 'Ollama Cloud', - description: 'Ollama.com cloud models (glm-4.7:cloud, qwen3-coder:480b)', + description: 'Ollama cloud models via direct API (glm-4.7:cloud, minimax-m2.1:cloud)', baseUrl: 'https://ollama.com', defaultProfileName: 'ollama-cloud', badge: 'Cloud', From 8e29c48c6dd689794adaf740634823914e609c9d Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 22 Jan 2026 15:17:06 -0500 Subject: [PATCH 08/11] fix(presets): make requiresApiKey required boolean, add sentinel docs - Change requiresApiKey from optional to required boolean in CLI - Add requiresApiKey: true to all existing presets for consistency - Document 'ollama' placeholder as sentinel value in code comment - Add trailing newline to ollama.svg --- src/api/services/provider-presets.ts | 13 +++++++++++-- src/commands/api-command.ts | 4 +++- ui/public/icons/ollama.svg | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/api/services/provider-presets.ts b/src/api/services/provider-presets.ts index d055e4a8..510246e4 100644 --- a/src/api/services/provider-presets.ts +++ b/src/api/services/provider-presets.ts @@ -17,8 +17,8 @@ export interface ProviderPreset { apiKeyPlaceholder: string; apiKeyHint: string; category: PresetCategory; - /** Set to false for local providers that don't need API key (default: true) */ - requiresApiKey?: boolean; + /** Whether API key is required (default: true, set false for local providers) */ + requiresApiKey: boolean; /** Additional env vars for thinking mode, etc. */ extraEnv?: Record; /** Enable always thinking mode */ @@ -44,6 +44,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'sk-or-...', apiKeyHint: 'Get your API key at openrouter.ai/keys', category: 'recommended', + requiresApiKey: true, }, { id: 'ollama', @@ -68,6 +69,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'ghp_...', apiKeyHint: 'Get your API key from Z.AI', category: 'alternative', + requiresApiKey: true, }, { id: 'glmt', @@ -79,6 +81,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'ghp_...', apiKeyHint: 'Same API key as GLM', category: 'alternative', + requiresApiKey: true, extraEnv: { ANTHROPIC_TEMPERATURE: '0.2', ANTHROPIC_MAX_TOKENS: '65536', @@ -99,6 +102,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'sk-...', apiKeyHint: 'Get your API key from Moonshot AI', category: 'alternative', + requiresApiKey: true, alwaysThinkingEnabled: true, }, { @@ -111,6 +115,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'YOUR_AZURE_API_KEY', apiKeyHint: 'Create resource at ai.azure.com, get API key from Keys tab', category: 'alternative', + requiresApiKey: true, }, { id: 'mm', @@ -122,6 +127,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'YOUR_MINIMAX_API_KEY_HERE', apiKeyHint: 'Get your API key at platform.minimax.io', category: 'alternative', + requiresApiKey: true, }, { id: 'deepseek', @@ -133,6 +139,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'sk-...', apiKeyHint: 'Get your API key at platform.deepseek.com', category: 'alternative', + requiresApiKey: true, }, { id: 'qwen', @@ -144,6 +151,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'sk-...', apiKeyHint: 'Get your API key from Alibaba Cloud Model Studio', category: 'alternative', + requiresApiKey: true, }, { id: 'ollama-cloud', @@ -155,6 +163,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ apiKeyPlaceholder: 'YOUR_OLLAMA_CLOUD_API_KEY', apiKeyHint: 'Get your API key at ollama.com', category: 'alternative', + requiresApiKey: true, }, ]; diff --git a/src/commands/api-command.ts b/src/commands/api-command.ts index 2e740ba6..df38438e 100644 --- a/src/commands/api-command.ts +++ b/src/commands/api-command.ts @@ -191,7 +191,9 @@ async function handleCreate(args: string[]): Promise { apiKey = parsedArgs.apiKey; } else { console.log(info('No API key required for local Ollama')); - apiKey = 'ollama'; // Placeholder token matching template + // Sentinel value 'ollama' matches config/base-ollama.settings.json template + // This is not a valid API key, just a placeholder for local-only providers + apiKey = 'ollama'; } } else if (!apiKey) { const keyPrompt = preset?.apiKeyHint ? `API Key (${preset.apiKeyHint})` : 'API Key'; diff --git a/ui/public/icons/ollama.svg b/ui/public/icons/ollama.svg index cc887e3d..c5e666a0 100644 --- a/ui/public/icons/ollama.svg +++ b/ui/public/icons/ollama.svg @@ -1 +1 @@ -Ollama \ No newline at end of file +Ollama From 49f3742f12ec091897b55c7b4faff72a0512d504 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 20:22:12 +0000 Subject: [PATCH 09/11] chore(release): 7.25.0-dev.1 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0657cb48..f2b2db91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.25.0", + "version": "7.25.0-dev.1", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", From 5074122d4af705933c60527d34f5ec9bc168990b Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Thu, 22 Jan 2026 16:29:30 -0500 Subject: [PATCH 10/11] feat(ui): add provider logos for alternative API presets Add icons for GLM, GLMT (Z.AI), Kimi, Azure Foundry, Minimax, DeepSeek, and Qwen presets. --- ui/public/icons/azure.svg | 1 + ui/public/icons/deepseek.svg | 1 + ui/public/icons/kimi.svg | 1 + ui/public/icons/minimax.svg | 1 + ui/public/icons/zai.svg | 1 + ui/src/lib/provider-presets.ts | 7 +++++++ 6 files changed, 12 insertions(+) create mode 100644 ui/public/icons/azure.svg create mode 100644 ui/public/icons/deepseek.svg create mode 100644 ui/public/icons/kimi.svg create mode 100644 ui/public/icons/minimax.svg create mode 100644 ui/public/icons/zai.svg diff --git a/ui/public/icons/azure.svg b/ui/public/icons/azure.svg new file mode 100644 index 00000000..41f503f5 --- /dev/null +++ b/ui/public/icons/azure.svg @@ -0,0 +1 @@ +AzureAI \ No newline at end of file diff --git a/ui/public/icons/deepseek.svg b/ui/public/icons/deepseek.svg new file mode 100644 index 00000000..3fc23024 --- /dev/null +++ b/ui/public/icons/deepseek.svg @@ -0,0 +1 @@ +DeepSeek \ No newline at end of file diff --git a/ui/public/icons/kimi.svg b/ui/public/icons/kimi.svg new file mode 100644 index 00000000..ec5db53a --- /dev/null +++ b/ui/public/icons/kimi.svg @@ -0,0 +1 @@ +Kimi \ No newline at end of file diff --git a/ui/public/icons/minimax.svg b/ui/public/icons/minimax.svg new file mode 100644 index 00000000..2a60bd47 --- /dev/null +++ b/ui/public/icons/minimax.svg @@ -0,0 +1 @@ +Minimax \ No newline at end of file diff --git a/ui/public/icons/zai.svg b/ui/public/icons/zai.svg new file mode 100644 index 00000000..04ba2d98 --- /dev/null +++ b/ui/public/icons/zai.svg @@ -0,0 +1 @@ +Z.ai \ No newline at end of file diff --git a/ui/src/lib/provider-presets.ts b/ui/src/lib/provider-presets.ts index 67c3f3ba..d6d35486 100644 --- a/ui/src/lib/provider-presets.ts +++ b/ui/src/lib/provider-presets.ts @@ -68,6 +68,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://api.z.ai/api/anthropic', defaultProfileName: 'glm', badge: 'Z.AI', + icon: '/icons/zai.svg', defaultModel: 'glm-4.7', requiresApiKey: true, apiKeyPlaceholder: 'ghp_...', @@ -81,6 +82,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://api.z.ai/api/coding/paas/v4/chat/completions', defaultProfileName: 'glmt', badge: 'Thinking', + icon: '/icons/zai.svg', defaultModel: 'glm-4.7', requiresApiKey: true, apiKeyPlaceholder: 'ghp_...', @@ -94,6 +96,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://api.kimi.com/coding/', defaultProfileName: 'kimi', badge: 'Reasoning', + icon: '/icons/kimi.svg', defaultModel: 'kimi-k2-thinking-turbo', requiresApiKey: true, apiKeyPlaceholder: 'sk-...', @@ -107,6 +110,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://.services.ai.azure.com/api/anthropic', defaultProfileName: 'foundry', badge: 'Azure', + icon: '/icons/azure.svg', defaultModel: 'claude-sonnet-4-5', requiresApiKey: true, apiKeyPlaceholder: 'YOUR_AZURE_API_KEY', @@ -120,6 +124,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://api.minimax.io/anthropic', defaultProfileName: 'mm', badge: '1M context', + icon: '/icons/minimax.svg', defaultModel: 'MiniMax-M2.1', requiresApiKey: true, apiKeyPlaceholder: 'YOUR_MINIMAX_API_KEY_HERE', @@ -133,6 +138,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://api.deepseek.com/anthropic', defaultProfileName: 'deepseek', badge: 'Reasoning', + icon: '/icons/deepseek.svg', defaultModel: 'deepseek-chat', requiresApiKey: true, apiKeyPlaceholder: 'sk-...', @@ -146,6 +152,7 @@ export const PROVIDER_PRESETS: ProviderPreset[] = [ baseUrl: 'https://dashscope-intl.aliyuncs.com/apps/anthropic', defaultProfileName: 'qwen', badge: 'Alibaba', + icon: '/assets/providers/qwen-color.svg', defaultModel: 'qwen3-coder-plus', requiresApiKey: true, apiKeyPlaceholder: 'sk-...', From 4fa519d8ae0768e18fd95f5ba4e787daa32bf91c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 21:45:26 +0000 Subject: [PATCH 11/11] chore(release): 7.25.0-dev.2 [skip ci] --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f2b2db91..b985c35d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "7.25.0-dev.1", + "version": "7.25.0-dev.2", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli",