Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/cli/handlers/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -143,6 +147,8 @@ function resolveInstallAgentKeys(flags: Map<string, string | boolean>): 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
Expand Down
7 changes: 4 additions & 3 deletions src/cli/skills.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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()
})

Expand Down
14 changes: 14 additions & 0 deletions src/shared/skills-cli-agent-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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)
}
})
})
18 changes: 18 additions & 0 deletions src/shared/skills-cli-agent-keys.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<string> = 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
}