Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
52a6c9020This file is pinned to the selected release. Download the original to open it in your design tool.
/**
* Official install recipes for every engine Harness can launch.
*
* The command is shown in Desktop before Create is pressed, then runs inside the tmux pane on the
* target machine. Keep every line tied to first-party documentation: a plausible package name that
* installs successfully but does not provide the expected executable is worse than no recipe.
*/
import { isTerminalEngine, type AgentEngine, type ProcessEngine } from '../engines/types.js'
/** How to find the executable after the installer returns. */
export interface EngineInstallExecutable {
/** Executable names published by the vendor, in preference order. */
readonly names: readonly string[]
/** Paths relative to the target user's home, used before a freshly edited PATH can be reloaded. */
readonly homeRelativePaths?: readonly string[]
/** Fixed system paths used by a vendor installer in a special mode, such as a root install. */
readonly absolutePaths?: readonly string[]
/** Resolve each name below `npm prefix -g` when the install method is npm. */
readonly npmGlobal?: boolean
}
export interface EngineInstallRecipe {
/** The first-party line a person would paste into a POSIX shell. */
readonly command: string
/** First-party documentation used to verify the command. */
readonly source: string
/** Ordered, source-owned ways to locate the binary after installation. */
readonly executable: EngineInstallExecutable
}
/**
* Exhaustive on purpose. Adding an engine without deciding how it is installed must fail typecheck
* rather than silently creating another unsupported row in Desktop.
*/
export const ENGINE_INSTALL: Readonly<Record<ProcessEngine, EngineInstallRecipe>> = {
claude: {
command: 'npm install -g @anthropic-ai/claude-code',
source: 'https://docs.anthropic.com/en/docs/claude-code/getting-started',
executable: { names: ['claude'], npmGlobal: true, homeRelativePaths: ['.local/bin/claude'] },
},
codex: {
command: 'npm install -g @openai/codex',
source: 'https://github.com/openai/codex',
executable: { names: ['codex'], npmGlobal: true, homeRelativePaths: ['.local/bin/codex'] },
},
cursor: {
command: 'curl https://cursor.com/install -fsS | bash',
source: 'https://docs.cursor.com/en/cli/installation',
// `cursor-agent` is deliberately the canonical name. Grok also installs `agent`; using that
// shared alias here could turn a requested Cursor pane into a Grok pane.
executable: { names: ['cursor-agent'], homeRelativePaths: ['.local/bin/cursor-agent'] },
},
opencode: {
command: 'npm install -g opencode-ai',
source: 'https://opencode.ai/docs',
executable: { names: ['opencode'], npmGlobal: true, homeRelativePaths: ['.opencode/bin/opencode'] },
},
pi: {
// pi.dev publishes the scoped package with --ignore-scripts. The unscoped package is unrelated
// and does not provide the `pi` executable.
command: 'npm install -g --ignore-scripts @earendil-works/pi-coding-agent',
source: 'https://pi.dev/',
executable: { names: ['pi'], npmGlobal: true },
},
hermes: {
command: 'curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash',
source: 'https://github.com/NousResearch/hermes-agent/blob/main/website/docs/getting-started/quickstart.md',
executable: {
names: ['hermes'],
homeRelativePaths: ['.local/bin/hermes'],
absolutePaths: ['/usr/local/bin/hermes'],
},
},
commandcode: {
command: 'npm i -g command-code',
source: 'https://commandcode.ai/docs',
executable: { names: ['cmd', 'command-code'], npmGlobal: true },
},
devin: {
command: 'curl -fsSL https://cli.devin.ai/install.sh | bash',
source: 'https://cli.devin.ai/reference/commands',
executable: { names: ['devin'], homeRelativePaths: ['.local/bin/devin'] },
},
muse: {
command: 'curl -fsSL https://dev.meta.ai/install.sh | bash',
source: 'https://ai.meta.com/llama/',
executable: { names: ['muse'], homeRelativePaths: ['.local/bin/muse'] },
},
amp: {
command: 'curl -fsSL https://ampcode.com/install.sh | bash',
source: 'https://ampcode.com/docs/cli',
executable: { names: ['amp'], homeRelativePaths: ['.local/bin/amp', '.amp/bin/amp'] },
},
kilo: {
command: 'npm install -g @kilocode/cli',
source: 'https://kilo.ai/docs/code-with-ai/platforms/cli',
executable: {
names: ['kilo', 'kilocode'],
npmGlobal: true,
homeRelativePaths: ['.kilo/bin/kilo', '.local/bin/kilo'],
},
},
grok: {
command: 'curl -fsSL https://x.ai/cli/install.sh | bash',
source: 'https://docs.x.ai/build/overview',
executable: { names: ['grok'], homeRelativePaths: ['.grok/bin/grok', '.local/bin/grok'] },
},
agy: {
command: 'curl -fsSL https://antigravity.google/cli/install.sh | bash',
source: 'https://antigravity.google/docs/cli/install/',
executable: { names: ['agy'], homeRelativePaths: ['.local/bin/agy'] },
},
copilot: {
command: 'npm install -g @github/copilot',
source: 'https://docs.github.com/en/copilot/get-started/cli-quickstart',
executable: { names: ['copilot'], npmGlobal: true, homeRelativePaths: ['.local/bin/copilot'] },
},
}
/** A terminal has nothing to install — the login shell is already there — hence `undefined`. */
export function engineInstallRecipe(engine: AgentEngine): EngineInstallRecipe | undefined {
return isTerminalEngine(engine) ? undefined : ENGINE_INSTALL[engine]
}
export const INSTALLABLE_ENGINES: ReadonlySet<AgentEngine> = new Set(
Object.keys(ENGINE_INSTALL) as AgentEngine[],
)