mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
fix(env): address P1-P3 review items from code review
- P1: Fix bash completion $cliproxy_profiles scoping — inline profiles in env block since variable is only defined at COMP_CWORD=1 scope - P2: Detect account-based profiles and show specific error message instead of generic "not found" - P2: Show `ccs migrate` hint when unified mode is disabled and settings profile resolution fails - P2: transformToOpenAI omits empty entries at transform time instead of relying on output filter (removes fragile coupling) - P3: Add zsh and auto to --shell completions across all 4 shells; map --shell zsh to bash in command handler since syntax is identical - P3: Auto-detect PowerShell from SHELL containing pwsh on non-Windows - Tests: 33 pass (+1 pwsh detection test, updated transform assertions)
This commit is contained in:
@@ -155,8 +155,8 @@ _ccs_completion() {
|
||||
if [[ ${COMP_WORDS[1]} == "env" ]]; then
|
||||
case "${prev}" in
|
||||
env)
|
||||
# Complete with profile names and flags
|
||||
local env_opts="--format --shell --help -h $cliproxy_profiles"
|
||||
# Complete with profile names and flags (inline profiles since $cliproxy_profiles is out of scope)
|
||||
local env_opts="--format --shell --help -h gemini codex agy qwen"
|
||||
if [[ -f ~/.ccs/config.json ]]; then
|
||||
env_opts="$env_opts $(jq -r '.profiles | keys[]' ~/.ccs/config.json 2>/dev/null || true)"
|
||||
fi
|
||||
@@ -168,7 +168,7 @@ _ccs_completion() {
|
||||
return 0
|
||||
;;
|
||||
--shell)
|
||||
COMPREPLY=( $(compgen -W "bash fish powershell" -- ${cur}) )
|
||||
COMPREPLY=( $(compgen -W "auto bash zsh fish powershell" -- ${cur}) )
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -176,7 +176,7 @@ complete -c ccs -n '__fish_seen_subcommand_from doctor' -s h -l help -d 'Show he
|
||||
complete -c ccs -n '__fish_seen_subcommand_from env' -l format -d 'Output format'
|
||||
complete -c ccs -n '__fish_seen_subcommand_from env; and __fish_seen_argument -l format' -a 'openai anthropic raw' -d 'Format'
|
||||
complete -c ccs -n '__fish_seen_subcommand_from env' -l shell -d 'Shell syntax'
|
||||
complete -c ccs -n '__fish_seen_subcommand_from env; and __fish_seen_argument -l shell' -a 'bash fish powershell' -d 'Shell'
|
||||
complete -c ccs -n '__fish_seen_subcommand_from env; and __fish_seen_argument -l shell' -a 'auto bash zsh fish powershell' -d 'Shell'
|
||||
complete -c ccs -n '__fish_seen_subcommand_from env' -s h -l help -d 'Show help for env command'
|
||||
|
||||
# ============================================================================
|
||||
|
||||
@@ -23,7 +23,7 @@ Register-ArgumentCompleter -CommandName ccs -ScriptBlock {
|
||||
$updateFlags = @('--force', '--beta', '--dev', '--help', '-h')
|
||||
$envFlags = @('--format', '--shell', '--help', '-h')
|
||||
$envFormats = @('openai', 'anthropic', 'raw')
|
||||
$envShells = @('bash', 'fish', 'powershell')
|
||||
$envShells = @('auto', 'bash', 'zsh', 'fish', 'powershell')
|
||||
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
|
||||
$listFlags = @('--verbose', '--json')
|
||||
$removeFlags = @('--yes', '-y')
|
||||
|
||||
@@ -128,7 +128,7 @@ _ccs() {
|
||||
env)
|
||||
_arguments \
|
||||
'--format[Output format]:format:(openai anthropic raw)' \
|
||||
'--shell[Shell syntax]:shell:(bash fish powershell)' \
|
||||
'--shell[Shell syntax]:shell:(auto bash zsh fish powershell)' \
|
||||
'(- *)'{-h,--help}'[Show help]' \
|
||||
'1:profile:($proxy_profiles ${(k)settings_profiles_described})'
|
||||
;;
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
||||
import { isUnifiedMode, loadUnifiedConfig } from '../config/unified-config-loader';
|
||||
import { expandPath } from '../utils/helpers';
|
||||
import { getCcsDir } from '../utils/config-manager';
|
||||
import { ProfileRegistry } from '../auth/profile-registry';
|
||||
|
||||
type ShellType = 'bash' | 'fish' | 'powershell';
|
||||
type OutputFormat = 'openai' | 'anthropic' | 'raw';
|
||||
@@ -28,7 +29,7 @@ export function detectShell(flag?: string): ShellType {
|
||||
}
|
||||
const shell = process.env['SHELL'] || '';
|
||||
if (shell.includes('fish')) return 'fish';
|
||||
if (process.platform === 'win32') return 'powershell';
|
||||
if (shell.includes('pwsh') || process.platform === 'win32') return 'powershell';
|
||||
return 'bash';
|
||||
}
|
||||
|
||||
@@ -54,11 +55,12 @@ export function transformToOpenAI(envVars: Record<string, string>): Record<strin
|
||||
const baseUrl = envVars['ANTHROPIC_BASE_URL'] || '';
|
||||
const apiKey = envVars['ANTHROPIC_AUTH_TOKEN'] || '';
|
||||
const model = envVars['ANTHROPIC_MODEL'] || '';
|
||||
const result: Record<string, string> = {
|
||||
OPENAI_API_KEY: apiKey,
|
||||
OPENAI_BASE_URL: baseUrl,
|
||||
LOCAL_ENDPOINT: baseUrl,
|
||||
};
|
||||
const result: Record<string, string> = {};
|
||||
if (apiKey) result['OPENAI_API_KEY'] = apiKey;
|
||||
if (baseUrl) {
|
||||
result['OPENAI_BASE_URL'] = baseUrl;
|
||||
result['LOCAL_ENDPOINT'] = baseUrl;
|
||||
}
|
||||
if (model) result['OPENAI_MODEL'] = model;
|
||||
return result;
|
||||
}
|
||||
@@ -195,7 +197,8 @@ export async function handleEnvCommand(args: string[]): Promise<void> {
|
||||
const format = formatStr as OutputFormat;
|
||||
|
||||
const shellStr = parseFlag(args, 'shell') || 'auto';
|
||||
const shell = detectShell(shellStr);
|
||||
// zsh uses the same syntax as bash
|
||||
const shell = detectShell(shellStr === 'zsh' ? 'bash' : shellStr);
|
||||
|
||||
// Resolve env vars based on profile type
|
||||
let envVars: Record<string, string> = {};
|
||||
@@ -212,9 +215,28 @@ export async function handleEnvCommand(args: string[]): Promise<void> {
|
||||
// Settings-based profile (glm, kimi, custom API)
|
||||
const resolved = resolveSettingsProfile(profile);
|
||||
if (!resolved) {
|
||||
// Check if it's an account-based profile
|
||||
const registry = new ProfileRegistry();
|
||||
const allProfiles = registry.getAllProfiles();
|
||||
if (allProfiles[profile]) {
|
||||
console.error(
|
||||
fail(
|
||||
`'${profile}' is an account-based profile. ` +
|
||||
'`ccs env` only supports CLIProxy and settings profiles.'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.error(fail(`Profile '${profile}' not found.`));
|
||||
console.error(dim(' Available CLIProxy profiles: ' + CLIPROXY_PROFILES.join(', ')));
|
||||
console.error(dim(` Check ${getCcsDir()}/config.yaml for custom profiles.`));
|
||||
if (!isUnifiedMode()) {
|
||||
console.error(
|
||||
dim(' Settings profiles require unified config. Run `ccs migrate` to upgrade.')
|
||||
);
|
||||
} else {
|
||||
console.error(dim(` Check ${getCcsDir()}/config.yaml for custom profiles.`));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
envVars = resolved;
|
||||
|
||||
@@ -60,6 +60,11 @@ describe('env-command', () => {
|
||||
process.env['SHELL'] = '/bin/bash';
|
||||
expect(detectShell('invalid')).toBe('bash');
|
||||
});
|
||||
|
||||
it('auto-detects powershell from SHELL containing pwsh', () => {
|
||||
process.env['SHELL'] = '/usr/local/bin/pwsh';
|
||||
expect(detectShell('auto')).toBe('powershell');
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatExportLine', () => {
|
||||
@@ -128,11 +133,7 @@ describe('env-command', () => {
|
||||
it('handles missing source vars gracefully', () => {
|
||||
const result = transformToOpenAI({});
|
||||
|
||||
expect(result).toEqual({
|
||||
OPENAI_API_KEY: '',
|
||||
OPENAI_BASE_URL: '',
|
||||
LOCAL_ENDPOINT: '',
|
||||
});
|
||||
expect(result).toEqual({});
|
||||
});
|
||||
|
||||
it('only extracts relevant vars', () => {
|
||||
@@ -143,7 +144,7 @@ describe('env-command', () => {
|
||||
DISABLE_TELEMETRY: '1',
|
||||
});
|
||||
|
||||
// Should only have 3 keys (no OPENAI_MODEL when ANTHROPIC_MODEL absent)
|
||||
// OPENAI_API_KEY + OPENAI_BASE_URL + LOCAL_ENDPOINT (no OPENAI_MODEL when ANTHROPIC_MODEL absent)
|
||||
expect(Object.keys(result)).toHaveLength(3);
|
||||
expect(result['ANTHROPIC_MAX_TOKENS']).toBeUndefined();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user