mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 10:19:37 +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
|
if [[ ${COMP_WORDS[1]} == "env" ]]; then
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
env)
|
env)
|
||||||
# Complete with profile names and flags
|
# Complete with profile names and flags (inline profiles since $cliproxy_profiles is out of scope)
|
||||||
local env_opts="--format --shell --help -h $cliproxy_profiles"
|
local env_opts="--format --shell --help -h gemini codex agy qwen"
|
||||||
if [[ -f ~/.ccs/config.json ]]; then
|
if [[ -f ~/.ccs/config.json ]]; then
|
||||||
env_opts="$env_opts $(jq -r '.profiles | keys[]' ~/.ccs/config.json 2>/dev/null || true)"
|
env_opts="$env_opts $(jq -r '.profiles | keys[]' ~/.ccs/config.json 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
@@ -168,7 +168,7 @@ _ccs_completion() {
|
|||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
--shell)
|
--shell)
|
||||||
COMPREPLY=( $(compgen -W "bash fish powershell" -- ${cur}) )
|
COMPREPLY=( $(compgen -W "auto bash zsh fish powershell" -- ${cur}) )
|
||||||
return 0
|
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' -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; 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' -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'
|
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')
|
$updateFlags = @('--force', '--beta', '--dev', '--help', '-h')
|
||||||
$envFlags = @('--format', '--shell', '--help', '-h')
|
$envFlags = @('--format', '--shell', '--help', '-h')
|
||||||
$envFormats = @('openai', 'anthropic', 'raw')
|
$envFormats = @('openai', 'anthropic', 'raw')
|
||||||
$envShells = @('bash', 'fish', 'powershell')
|
$envShells = @('auto', 'bash', 'zsh', 'fish', 'powershell')
|
||||||
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
|
$shellCompletionFlags = @('--bash', '--zsh', '--fish', '--powershell')
|
||||||
$listFlags = @('--verbose', '--json')
|
$listFlags = @('--verbose', '--json')
|
||||||
$removeFlags = @('--yes', '-y')
|
$removeFlags = @('--yes', '-y')
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ _ccs() {
|
|||||||
env)
|
env)
|
||||||
_arguments \
|
_arguments \
|
||||||
'--format[Output format]:format:(openai anthropic raw)' \
|
'--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]' \
|
'(- *)'{-h,--help}'[Show help]' \
|
||||||
'1:profile:($proxy_profiles ${(k)settings_profiles_described})'
|
'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 { isUnifiedMode, loadUnifiedConfig } from '../config/unified-config-loader';
|
||||||
import { expandPath } from '../utils/helpers';
|
import { expandPath } from '../utils/helpers';
|
||||||
import { getCcsDir } from '../utils/config-manager';
|
import { getCcsDir } from '../utils/config-manager';
|
||||||
|
import { ProfileRegistry } from '../auth/profile-registry';
|
||||||
|
|
||||||
type ShellType = 'bash' | 'fish' | 'powershell';
|
type ShellType = 'bash' | 'fish' | 'powershell';
|
||||||
type OutputFormat = 'openai' | 'anthropic' | 'raw';
|
type OutputFormat = 'openai' | 'anthropic' | 'raw';
|
||||||
@@ -28,7 +29,7 @@ export function detectShell(flag?: string): ShellType {
|
|||||||
}
|
}
|
||||||
const shell = process.env['SHELL'] || '';
|
const shell = process.env['SHELL'] || '';
|
||||||
if (shell.includes('fish')) return 'fish';
|
if (shell.includes('fish')) return 'fish';
|
||||||
if (process.platform === 'win32') return 'powershell';
|
if (shell.includes('pwsh') || process.platform === 'win32') return 'powershell';
|
||||||
return 'bash';
|
return 'bash';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,11 +55,12 @@ export function transformToOpenAI(envVars: Record<string, string>): Record<strin
|
|||||||
const baseUrl = envVars['ANTHROPIC_BASE_URL'] || '';
|
const baseUrl = envVars['ANTHROPIC_BASE_URL'] || '';
|
||||||
const apiKey = envVars['ANTHROPIC_AUTH_TOKEN'] || '';
|
const apiKey = envVars['ANTHROPIC_AUTH_TOKEN'] || '';
|
||||||
const model = envVars['ANTHROPIC_MODEL'] || '';
|
const model = envVars['ANTHROPIC_MODEL'] || '';
|
||||||
const result: Record<string, string> = {
|
const result: Record<string, string> = {};
|
||||||
OPENAI_API_KEY: apiKey,
|
if (apiKey) result['OPENAI_API_KEY'] = apiKey;
|
||||||
OPENAI_BASE_URL: baseUrl,
|
if (baseUrl) {
|
||||||
LOCAL_ENDPOINT: baseUrl,
|
result['OPENAI_BASE_URL'] = baseUrl;
|
||||||
};
|
result['LOCAL_ENDPOINT'] = baseUrl;
|
||||||
|
}
|
||||||
if (model) result['OPENAI_MODEL'] = model;
|
if (model) result['OPENAI_MODEL'] = model;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -195,7 +197,8 @@ export async function handleEnvCommand(args: string[]): Promise<void> {
|
|||||||
const format = formatStr as OutputFormat;
|
const format = formatStr as OutputFormat;
|
||||||
|
|
||||||
const shellStr = parseFlag(args, 'shell') || 'auto';
|
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
|
// Resolve env vars based on profile type
|
||||||
let envVars: Record<string, string> = {};
|
let envVars: Record<string, string> = {};
|
||||||
@@ -212,9 +215,28 @@ export async function handleEnvCommand(args: string[]): Promise<void> {
|
|||||||
// Settings-based profile (glm, kimi, custom API)
|
// Settings-based profile (glm, kimi, custom API)
|
||||||
const resolved = resolveSettingsProfile(profile);
|
const resolved = resolveSettingsProfile(profile);
|
||||||
if (!resolved) {
|
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(fail(`Profile '${profile}' not found.`));
|
||||||
console.error(dim(' Available CLIProxy profiles: ' + CLIPROXY_PROFILES.join(', ')));
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
envVars = resolved;
|
envVars = resolved;
|
||||||
|
|||||||
@@ -60,6 +60,11 @@ describe('env-command', () => {
|
|||||||
process.env['SHELL'] = '/bin/bash';
|
process.env['SHELL'] = '/bin/bash';
|
||||||
expect(detectShell('invalid')).toBe('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', () => {
|
describe('formatExportLine', () => {
|
||||||
@@ -128,11 +133,7 @@ describe('env-command', () => {
|
|||||||
it('handles missing source vars gracefully', () => {
|
it('handles missing source vars gracefully', () => {
|
||||||
const result = transformToOpenAI({});
|
const result = transformToOpenAI({});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({});
|
||||||
OPENAI_API_KEY: '',
|
|
||||||
OPENAI_BASE_URL: '',
|
|
||||||
LOCAL_ENDPOINT: '',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only extracts relevant vars', () => {
|
it('only extracts relevant vars', () => {
|
||||||
@@ -143,7 +144,7 @@ describe('env-command', () => {
|
|||||||
DISABLE_TELEMETRY: '1',
|
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(Object.keys(result)).toHaveLength(3);
|
||||||
expect(result['ANTHROPIC_MAX_TOKENS']).toBeUndefined();
|
expect(result['ANTHROPIC_MAX_TOKENS']).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user