diff --git a/src/targets/target-resolver.ts b/src/targets/target-resolver.ts index 3aa06441..edc23481 100644 --- a/src/targets/target-resolver.ts +++ b/src/targets/target-resolver.ts @@ -18,6 +18,25 @@ import { TargetType } from './target-adapter'; const ARGV0_TARGET_MAP: Record = { ccsd: 'droid', }; +const ALIAS_NAME_REGEX = /^[a-z0-9._-]+$/; + +function buildArgv0TargetMap(): Record { + const map: Record = { ...ARGV0_TARGET_MAP }; + const envAliases = process.env['CCS_DROID_ALIASES']; + if (!envAliases) { + return map; + } + + for (const rawAlias of envAliases.split(',')) { + const alias = rawAlias.trim().toLowerCase(); + if (!alias || !ALIAS_NAME_REGEX.test(alias)) { + continue; + } + map[alias] = 'droid'; + } + + return map; +} /** * Valid target types for --target flag validation. @@ -108,8 +127,9 @@ export function resolveTargetType( // 3. Check argv[0] (busybox pattern) // Strip common wrapper extensions for Windows shims/wrappers const rawBin = path.basename(process.argv[1] || process.argv0 || ''); - const binName = rawBin.replace(/\.(cmd|bat|ps1|exe)$/i, ''); - const argv0Target = ARGV0_TARGET_MAP[binName]; + const binName = rawBin.replace(/\.(cmd|bat|ps1|exe)$/i, '').toLowerCase(); + const argv0TargetMap = buildArgv0TargetMap(); + const argv0Target = argv0TargetMap[binName]; if (argv0Target) { return argv0Target; } diff --git a/tests/unit/targets/target-resolver.test.ts b/tests/unit/targets/target-resolver.test.ts index cfed158d..a4082cdf 100644 --- a/tests/unit/targets/target-resolver.test.ts +++ b/tests/unit/targets/target-resolver.test.ts @@ -6,9 +6,15 @@ import { resolveTargetType, stripTargetFlag } from '../../../src/targets/target- describe('resolveTargetType', () => { const originalArgv = process.argv; + const originalDroidAliases = process.env.CCS_DROID_ALIASES; afterEach(() => { process.argv = originalArgv; + if (originalDroidAliases === undefined) { + delete process.env.CCS_DROID_ALIASES; + } else { + process.env.CCS_DROID_ALIASES = originalDroidAliases; + } }); it('should return claude as default', () => { @@ -41,6 +47,24 @@ describe('resolveTargetType', () => { expect(resolveTargetType([])).toBe('droid'); }); + it('should detect custom argv[0] aliases from CCS_DROID_ALIASES', () => { + process.env.CCS_DROID_ALIASES = 'droidx,my-droid'; + process.argv = ['node', 'my-droid']; + expect(resolveTargetType([])).toBe('droid'); + }); + + it('should ignore invalid custom alias entries', () => { + process.env.CCS_DROID_ALIASES = 'valid_alias,../bad,'; + process.argv = ['node', '../bad']; + expect(resolveTargetType([])).toBe('claude'); + }); + + it('should normalize argv[0] and custom aliases case-insensitively', () => { + process.env.CCS_DROID_ALIASES = 'DroidCaps'; + process.argv = ['node', 'DROIDCAPS']; + expect(resolveTargetType([])).toBe('droid'); + }); + it('should strip .cmd extension on Windows argv[0]', () => { process.argv = ['node', 'ccsd.cmd']; expect(resolveTargetType([])).toBe('droid');