mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 00:02:19 +00:00
Adds `orca skills install` and `orca skills update` so skills can be set up without the GUI — SSH hosts, containers, CI. Previously `orca skills` had only `list` and `get`, so there was no headless path. **Agent targeting is scoped explicitly rather than delegated to detection.** The `skills` CLI decides which agents to install into, and with `-y` and zero detected agents it takes `targetAgents = validAgents` — all ~75. That is not a corner case for a headless CLI: a fresh SSH box or container with no agent installed is the normal starting state. Measured on a bare host, the unscoped command created **52 top-level agent directories and 54 junctions** (one real payload in `~/.agents/skills`, the rest links) on Windows, and 52/53 on macOS. The CLI now passes `--agent` derived from Orca's own detection, mapped to the `skills` key namespace, plus `universal`. Supplying `--agent` makes `runAdd` use it directly and never call `detectInstalledAgents()`, so the fan-out branch is unreachable. On a bare host it now refuses with `No coding agent detected on this host` and exit 1, creating nothing. Same command with scoping: **1 directory, 0 junctions.** `universal` alone would under-install — Claude Code is not in that set, and 19 of 28 mapped keys write agent-private homes `universal` never touches. `--agent '*'` is the bug itself. The mapping is hedged three ways: `null` for any agent whose key could not be confirmed, `satisfies Record<TuiAgent, …>` so a new Orca agent is a compile error, and a test pinning every mapped key against the CLI's own valid list. Fixed during review — two holes that each restored the full fan-out through a different door: - `--agent ','` trimmed to nothing, which skipped the refusal *and* emitted no `--agent`. - `--agent -y` passed an emptiness check, and the vendor CLI silently drops `-`-leading values, re-emptying its list. The real invariant is argument *shape*, not emptiness, and it is now enforced at the choke point in `buildAgentFeatureSkillInstallArgs`, so no caller can emit `-y` without a usable target. `*` remains allowed — asking for every agent explicitly is a choice, not an accident. Verified with 51 hostile inputs through the built binary, each recorded argv replayed through the vendor's own parser. Also fixed: the `ORCA_CLI_CWD` refusal now runs before target resolution (it was quoting the wrong host's agent list), and `--dry-run` is refused in a forwarded shell rather than printing a command naming the wrong machine. Validated on a real Windows host across PowerShell 7, PowerShell 5.1, cmd.exe and Git Bash: `.cmd` shims route through `cmd.exe` and `.exe` shims spawn directly (proved with instrumented shims, not inferred), the ENOENT path produces an actionable error rather than a silent failure, and `skills update` genuinely restores a corrupted skill byte-for-byte. Known, not addressed here — both upstream behaviours this only forwards: a partial install failure exits 0, and "no installed skills found" exits 0. Both are invisible to the headless callers this feature exists for. Co-authored-by: scastanoh21 <scastanoh21@gmail.com>
133 lines
5.5 KiB
TypeScript
133 lines
5.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
buildAgentFeatureSkillInstallArgs,
|
|
buildAgentFeatureSkillInstallCommand,
|
|
ORCA_CLI_SKILL_INSTALL_COMMAND,
|
|
buildAgentFeatureSkillUpdateArgs,
|
|
buildAgentFeatureSkillUpdateCommand,
|
|
COMPUTER_USE_SKILL_UPDATE_COMMAND,
|
|
EPHEMERAL_VMS_SKILL_UPDATE_COMMAND,
|
|
LINEAR_TICKETS_SKILL_UPDATE_COMMAND,
|
|
ORCA_LINEAR_SKILL_UPDATE_COMMAND,
|
|
ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND,
|
|
ORCA_CLI_SKILL_UPDATE_COMMAND,
|
|
ORCHESTRATION_SKILL_UPDATE_COMMAND
|
|
} from './agent-feature-install-commands'
|
|
|
|
describe('agent feature skill commands', () => {
|
|
it('builds a global install command by default', () => {
|
|
expect(buildAgentFeatureSkillInstallCommand(['orca-cli'])).toBe(
|
|
'npx skills add https://github.com/stablyai/orca --skill orca-cli --global'
|
|
)
|
|
})
|
|
|
|
it('drops --global when installing locally', () => {
|
|
expect(buildAgentFeatureSkillInstallCommand(['orca-cli'], { global: false })).toBe(
|
|
'npx skills add https://github.com/stablyai/orca --skill orca-cli'
|
|
)
|
|
})
|
|
|
|
it('repeats --skill per name for multi-skill installs', () => {
|
|
expect(buildAgentFeatureSkillInstallCommand(['orca-cli', 'orchestration'])).toBe(
|
|
'npx skills add https://github.com/stablyai/orca --skill orca-cli --skill orchestration --global'
|
|
)
|
|
expect(buildAgentFeatureSkillInstallArgs(['orca-cli', 'orchestration'])).toEqual([
|
|
'skills',
|
|
'add',
|
|
'https://github.com/stablyai/orca',
|
|
'--skill',
|
|
'orca-cli',
|
|
'--skill',
|
|
'orchestration',
|
|
'--global'
|
|
])
|
|
})
|
|
|
|
it('keeps the copyable Settings commands interactive by default', () => {
|
|
// Why: -y skips the agent picker. A human pasting from Settings should still
|
|
// get it; only an unattended spawn opts in.
|
|
expect(buildAgentFeatureSkillInstallCommand(['orca-cli'])).not.toContain('-y')
|
|
expect(buildAgentFeatureSkillUpdateCommand('orca-cli')).not.toContain('-y')
|
|
expect(ORCA_CLI_SKILL_INSTALL_COMMAND).not.toContain('-y')
|
|
expect(ORCA_CLI_SKILL_UPDATE_COMMAND).not.toContain('-y')
|
|
})
|
|
|
|
it('refuses to skip prompts without an install target', () => {
|
|
// Why: -y with no --agent is the one combination that makes `skills add`
|
|
// install into every agent it knows (~75). No caller may express it.
|
|
expect(() => buildAgentFeatureSkillInstallCommand(['orca-cli'], { yes: true })).toThrow(
|
|
'An install target is required when skipping prompts.'
|
|
)
|
|
})
|
|
|
|
it('refuses a target the skills CLI would drop', () => {
|
|
// Why: defence in depth behind the CLI's own check — the skills CLI silently
|
|
// drops a `-`-leading --agent value, which empties its target list and
|
|
// installs into every agent it knows.
|
|
expect(() =>
|
|
buildAgentFeatureSkillInstallCommand(['orca-cli'], { yes: true, agents: ['-y'] })
|
|
).toThrow('"-y" is not a usable install target.')
|
|
expect(() =>
|
|
buildAgentFeatureSkillInstallArgs(['orca-cli'], { yes: true, agents: ['universal', 'a b'] })
|
|
).toThrow('"a b" is not a usable install target.')
|
|
})
|
|
|
|
it('appends -y and the targets for an unattended run', () => {
|
|
expect(
|
|
buildAgentFeatureSkillInstallCommand(['orca-cli'], { yes: true, agents: ['universal'] })
|
|
).toBe(
|
|
'npx skills add https://github.com/stablyai/orca --skill orca-cli --global --agent universal -y'
|
|
)
|
|
expect(buildAgentFeatureSkillUpdateCommand(['orca-cli'], { global: false, yes: true })).toBe(
|
|
'npx skills update orca-cli --project -y'
|
|
)
|
|
expect(
|
|
buildAgentFeatureSkillInstallArgs(['orca-cli'], { yes: true, agents: ['universal'] }).at(-1)
|
|
).toBe('-y')
|
|
expect(buildAgentFeatureSkillUpdateArgs(['orca-cli'], { yes: true }).at(-1)).toBe('-y')
|
|
})
|
|
|
|
it('builds single-skill update commands', () => {
|
|
expect(buildAgentFeatureSkillUpdateCommand('orchestration')).toBe(
|
|
'npx skills update orchestration --global'
|
|
)
|
|
})
|
|
|
|
it('trims and rejects blank update skill names', () => {
|
|
expect(buildAgentFeatureSkillUpdateCommand(' orca-cli ')).toBe(
|
|
'npx skills update orca-cli --global'
|
|
)
|
|
expect(() => buildAgentFeatureSkillUpdateCommand(' ')).toThrow('A skill name is required.')
|
|
})
|
|
|
|
it('builds multi-skill update commands and selects project scope for --local', () => {
|
|
expect(buildAgentFeatureSkillUpdateCommand(['orca-cli', 'orchestration'])).toBe(
|
|
'npx skills update orca-cli orchestration --global'
|
|
)
|
|
expect(buildAgentFeatureSkillUpdateCommand(['orca-cli'], { global: false })).toBe(
|
|
'npx skills update orca-cli --project'
|
|
)
|
|
expect(buildAgentFeatureSkillUpdateArgs(['orca-cli'], { global: false })).toEqual([
|
|
'skills',
|
|
'update',
|
|
'orca-cli',
|
|
'--project'
|
|
])
|
|
expect(() => buildAgentFeatureSkillUpdateCommand([])).toThrow('A skill name is required.')
|
|
})
|
|
|
|
it('exports single-skill update constants without changing install bundles', () => {
|
|
expect(ORCA_CLI_SKILL_UPDATE_COMMAND).toBe('npx skills update orca-cli --global')
|
|
expect(COMPUTER_USE_SKILL_UPDATE_COMMAND).toBe('npx skills update computer-use --global')
|
|
expect(ORCHESTRATION_SKILL_UPDATE_COMMAND).toBe('npx skills update orchestration --global')
|
|
expect(EPHEMERAL_VMS_SKILL_UPDATE_COMMAND).toBe(
|
|
'npx skills update orca-per-workspace-env --global'
|
|
)
|
|
expect(ORCA_LINEAR_SKILL_UPDATE_COMMAND).toBe('npx skills update orca-linear --global')
|
|
expect(LINEAR_TICKETS_SKILL_UPDATE_COMMAND).toBe('npx skills update linear-tickets --global')
|
|
expect(ORCA_CLI_ORCHESTRATION_SKILL_INSTALL_COMMAND).toBe(
|
|
buildAgentFeatureSkillInstallCommand(['orca-cli', 'orchestration'])
|
|
)
|
|
})
|
|
})
|