diff --git a/src/cli/handlers/skills.ts b/src/cli/handlers/skills.ts index 325262a42f5f..cbd4a3a0590f 100644 --- a/src/cli/handlers/skills.ts +++ b/src/cli/handlers/skills.ts @@ -17,7 +17,11 @@ import { UnsafeWindowsBatchArgumentsError, WINDOWS_BATCH_UNSAFE_CHARACTERS_LABEL } from '../../shared/windows-batch-spawn' -import { isUsableSkillsCliAgentKey, toSkillsCliAgentKeys } from '../../shared/skills-cli-agent-keys' +import { + isUsableSkillsCliAgentKey, + toRequestedSkillsCliAgentKey, + toSkillsCliAgentKeys +} from '../../shared/skills-cli-agent-keys' import { buildAgentFeatureSkillInstallArgs, buildAgentFeatureSkillUpdateArgs @@ -143,6 +147,8 @@ function resolveInstallAgentKeys(flags: Map): string[] .split(',') .map((entry) => entry.trim()) .filter(Boolean) + // Why: dedupe after mapping so `claude,claude-code` yields one target. + .map(toRequestedSkillsCliAgentKey) ) ] // Why: a value like "," parses to nothing. Falling through to detection would diff --git a/src/cli/skills.test.ts b/src/cli/skills.test.ts index 19b26037d923..77b8f5112f29 100644 --- a/src/cli/skills.test.ts +++ b/src/cli/skills.test.ts @@ -778,7 +778,7 @@ describe('orca skills CLI', () => { vi.spyOn(process.stderr, 'write').mockImplementation(() => true) const resultPromise = main( - ['skills', 'install', '--skill', 'alpha', '--agent', 'codex, claude-code ,codex'], + ['skills', 'install', '--skill', 'alpha', '--agent', 'codex, claude-code ,codex,claude,trae'], '/tmp/repo' ) await vi.waitFor(() => expect(spawnMock).toHaveBeenCalled()) @@ -787,8 +787,9 @@ describe('orca skills CLI', () => { const argv = spawnMock.mock.calls[0]?.[1] ?? [] const agents = argv.filter((_: string, i: number) => argv[i - 1] === '--agent') - // Why: trimmed and de-duplicated, and detection is not consulted at all. - expect(agents).toEqual(['codex', 'claude-code']) + // Why: trimmed and de-duplicated, and detection is not consulted at all. Orca's + // `claude` becomes `claude-code`; `trae` is the CLI's own key, so it is kept. + expect(agents).toEqual(['codex', 'claude-code', 'trae']) expect(detectCommandsMock).not.toHaveBeenCalled() }) diff --git a/src/shared/skills-cli-agent-keys.test.ts b/src/shared/skills-cli-agent-keys.test.ts index 02bdd8b52dfd..84c8b582064e 100644 --- a/src/shared/skills-cli-agent-keys.test.ts +++ b/src/shared/skills-cli-agent-keys.test.ts @@ -4,6 +4,7 @@ import { SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT, isUsableSkillsCliAgentKey, SKILLS_CLI_UNIVERSAL_AGENT_KEY, + toRequestedSkillsCliAgentKey, toSkillsCliAgentKeys } from './skills-cli-agent-keys' @@ -134,4 +135,17 @@ describe('skills CLI agent keys', () => { expect(toSkillsCliAgentKeys(['omp'])).toEqual(['universal']) expect(toSkillsCliAgentKeys([])).toEqual(['universal']) }) + + it('maps a typed Orca agent id unless the skills CLI already owns that key', () => { + for (const [agent, key] of Object.entries(SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT)) { + // Why: an id that is itself a valid skills key (only `trae`) keeps the CLI's meaning. + const expected = key === null || SKILLS_CLI_VALID_AGENT_KEYS.has(agent) ? agent : key + expect(toRequestedSkillsCliAgentKey(agent), agent).toBe(expected) + } + expect(toRequestedSkillsCliAgentKey('claude')).toBe('claude-code') + expect(toRequestedSkillsCliAgentKey('trae')).toBe('trae') + for (const passthrough of ['claude-code', 'universal', '*', 'Claude', 'inference-sh']) { + expect(toRequestedSkillsCliAgentKey(passthrough), passthrough).toBe(passthrough) + } + }) }) diff --git a/src/shared/skills-cli-agent-keys.ts b/src/shared/skills-cli-agent-keys.ts index 2db871a02846..a76cc650b0bb 100644 --- a/src/shared/skills-cli-agent-keys.ts +++ b/src/shared/skills-cli-agent-keys.ts @@ -1,4 +1,5 @@ import type { TuiAgent } from './tui-agent' +import { isTuiAgent } from './tui-agent-config' /** * The community `skills` CLI's own `--agent` key for each agent Orca detects. @@ -93,3 +94,20 @@ export function toSkillsCliAgentKeys(detectedAgents: readonly TuiAgent[]): strin } return [...keys].sort() } + +// Why: `trae` is also the skills CLI's own key for TRAE international, so a typed +// `trae` keeps that meaning rather than becoming the detected-agent target `trae-cn`. +const SKILLS_CLI_KEYS_SHADOWING_TUI_AGENTS: ReadonlySet = new Set(['trae']) + +/** + * Map a user-typed `--agent` value onto its skills key when it is an Orca agent id. + * + * Why: users type the id Orca accepts everywhere else (`claude`), which the skills CLI + * rejects. Values Orca cannot map pass through for the CLI to judge, as before. + */ +export function toRequestedSkillsCliAgentKey(value: string): string { + if (!isTuiAgent(value) || SKILLS_CLI_KEYS_SHADOWING_TUI_AGENTS.has(value)) { + return value + } + return SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT[value] ?? value +}