From 7337cab2d8b3402292478ea137b6bd034541ebf6 Mon Sep 17 00:00:00 2001 From: Emad Fussi <43019044+omd0@users.noreply.github.com> Date: Sat, 30 May 2026 02:57:56 +0300 Subject: [PATCH] feat(linux): add RPM package target and rename CLI to orca-ide Adds an RPM Linux package target and renames the Linux CLI command to orca-ide to avoid shadowing GNOME Orca, while preserving macOS and Windows CLI command behavior.\n\nFollow-up hardening keeps the Linux launcher executable, removes only old Orca-managed Linux/WSL orca launchers during migration, preserves AppImage/deb artifact names, and updates package/release tests for the RPM asset. --- .github/workflows/release-cut.yml | 4 +- config/electron-builder.config.cjs | 11 +- .../scripts/electron-builder-config.test.mjs | 10 ++ .../verify-release-required-assets.mjs | 1 + .../verify-release-required-assets.test.mjs | 10 ++ resources/linux/bin/{orca => orca-ide} | 2 +- skills/orca-cli/SKILL.md | 31 ++-- skills/orchestration/SKILL.md | 4 +- src/main/cli/cli-installer.test.ts | 169 +++++++++++------- src/main/cli/cli-installer.ts | 58 +++++- src/main/cli/packaged-cli-assets.test.ts | 13 +- src/main/cli/wsl-cli-installer.test.ts | 16 +- src/main/cli/wsl-cli-installer.ts | 22 ++- src/main/cli/wsl-cli-scripts.ts | 4 +- .../FloatingTerminalOrchestrationDialog.tsx | 6 +- .../onboarding/FeatureSetupChecklist.tsx | 4 +- .../components/settings/BrowserUsePane.tsx | 8 +- .../src/components/settings/CliSection.tsx | 25 ++- .../settings/WslCliRegistration.tsx | 21 ++- .../components/settings/browser-use-search.ts | 2 +- .../src/components/settings/general-search.ts | 4 +- .../src/lib/agent-skill-cli-prerequisite.ts | 10 +- src/renderer/src/web/web-preload-api.ts | 2 +- 23 files changed, 303 insertions(+), 134 deletions(-) rename resources/linux/bin/{orca => orca-ide} (94%) mode change 100644 => 100755 diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index d115007a0..a306d1c62 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -548,9 +548,11 @@ jobs: # Why: `pnpm build:release` verifies the Linux computer-use provider by # importing AT-SPI bindings, which are runtime package deps but are not # present on stock GitHub Ubuntu release runners. + # Why: `rpm` is needed by electron-builder's fpm backend to produce the + # .rpm artifact. Stock Ubuntu runners do not ship it. - name: Install Linux computer-use provider dependencies if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y python3-gi gir1.2-atspi-2.0 at-spi2-core xclip xdotool + run: sudo apt-get update && sudo apt-get install -y python3-gi gir1.2-atspi-2.0 at-spi2-core xclip xdotool rpm - name: Verify macOS signing environment if: matrix.platform == 'mac' diff --git a/config/electron-builder.config.cjs b/config/electron-builder.config.cjs index 5177a7a7a..b4017a7f9 100644 --- a/config/electron-builder.config.cjs +++ b/config/electron-builder.config.cjs @@ -200,8 +200,8 @@ module.exports = { extraResources: [ relayExtraResource, { - from: 'resources/linux/bin/orca', - to: 'bin/orca' + from: 'resources/linux/bin/orca-ide', + to: 'bin/orca-ide' }, { from: 'node_modules/agent-browser/bin/agent-browser-linux-${arch}', @@ -213,7 +213,7 @@ module.exports = { }, featureWallResources ], - target: ['AppImage', 'deb'], + target: ['AppImage', 'deb', 'rpm'], maintainer: 'stablyai', category: 'Utility' }, @@ -225,6 +225,11 @@ module.exports = { artifactName: 'orca-ide_${version}_${arch}.${ext}', depends: ['python3', 'python3-gi', 'gir1.2-atspi-2.0', 'at-spi2-core', 'xdotool', 'xclip'] }, + rpm: { + packageName: 'orca-ide', + artifactName: 'orca-ide-${version}.${arch}.${ext}', + depends: ['python3', 'python3-gobject', 'at-spi2-core', 'xdotool', 'xclip'] + }, // Why: must be true so that electron-builder rebuilds native modules // (node-pty) for each target architecture when producing dual-arch macOS // builds (x64 + arm64). With npmRebuild disabled, CI on an arm64 runner diff --git a/config/scripts/electron-builder-config.test.mjs b/config/scripts/electron-builder-config.test.mjs index 453e9f632..4e4938a35 100644 --- a/config/scripts/electron-builder-config.test.mjs +++ b/config/scripts/electron-builder-config.test.mjs @@ -8,4 +8,14 @@ describe('electron-builder config', () => { it('uses the multi-size icon source for Linux packages', () => { expect(electronBuilderConfig.linux.icon).toBe('resources/build/icon.icns') }) + + it('builds RPMs without changing existing Linux artifact names', () => { + expect(electronBuilderConfig.linux.target).toEqual(['AppImage', 'deb', 'rpm']) + expect(electronBuilderConfig.appImage.artifactName).toBe('orca-linux.${ext}') + expect(electronBuilderConfig.deb.artifactName).toBe('orca-ide_${version}_${arch}.${ext}') + expect(electronBuilderConfig.rpm).toMatchObject({ + packageName: 'orca-ide', + artifactName: 'orca-ide-${version}.${arch}.${ext}' + }) + }) }) diff --git a/config/scripts/verify-release-required-assets.mjs b/config/scripts/verify-release-required-assets.mjs index 12dbdb53b..5c78533b5 100644 --- a/config/scripts/verify-release-required-assets.mjs +++ b/config/scripts/verify-release-required-assets.mjs @@ -12,6 +12,7 @@ export function getRequiredReleaseAssetNames(tag) { 'latest.yml', 'orca-linux.AppImage', `orca-ide_${version}_amd64.deb`, + `orca-ide-${version}.x86_64.rpm`, 'orca-windows-setup.exe', 'orca-windows-setup.exe.blockmap', `Orca-${version}-mac.zip`, diff --git a/config/scripts/verify-release-required-assets.test.mjs b/config/scripts/verify-release-required-assets.test.mjs index 06a853e11..1caac947d 100644 --- a/config/scripts/verify-release-required-assets.test.mjs +++ b/config/scripts/verify-release-required-assets.test.mjs @@ -44,6 +44,16 @@ describe('getRequiredReleaseAssetNames', () => { ]) ) }) + + it('includes the Linux RPM alongside the existing AppImage and deb names', () => { + expect(getRequiredReleaseAssetNames('v1.4.27')).toEqual( + expect.arrayContaining([ + 'orca-linux.AppImage', + 'orca-ide_1.4.27_amd64.deb', + 'orca-ide-1.4.27.x86_64.rpm' + ]) + ) + }) }) describe('extractManifestAssetNames', () => { diff --git a/resources/linux/bin/orca b/resources/linux/bin/orca-ide old mode 100644 new mode 100755 similarity index 94% rename from resources/linux/bin/orca rename to resources/linux/bin/orca-ide index 1a4f8b316..94609a362 --- a/resources/linux/bin/orca +++ b/resources/linux/bin/orca-ide @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -# Why: invoked via ~/.local/bin/orca symlink (from CliInstaller); resolve it +# Why: invoked via ~/.local/bin/orca-ide symlink (from CliInstaller); resolve it # so APP_DIR points at the real install, not the symlink's parent. SOURCE="${BASH_SOURCE[0]}" while [ -h "$SOURCE" ]; do diff --git a/skills/orca-cli/SKILL.md b/skills/orca-cli/SKILL.md index e7b5a4eaf..7e5f3a212 100644 --- a/skills/orca-cli/SKILL.md +++ b/skills/orca-cli/SKILL.md @@ -20,9 +20,13 @@ description: >- Use this skill when the task should go through Orca's control plane rather than directly through `git`, shell PTYs, or ad hoc filesystem access. +## Platform Note + +On Linux, the CLI command is `orca-ide` (not `orca`) to avoid conflicting with GNOME Orca, the accessibility screen reader. Everywhere this document says `orca `, Linux users should substitute `orca-ide `. macOS and Windows are unaffected. + ## When To Use -Use `orca` for: +Use `orca` (or `orca-ide` on Linux) for: - worktree orchestration inside a running Orca app - updating the current worktree comment with meaningful progress checkpoints @@ -30,7 +34,7 @@ Use `orca` for: - stopping or waiting on Orca-managed terminals - creating and managing scheduled Orca automations - accessing repos known to Orca -Do not use `orca` when plain shell tools are simpler and Orca state does not matter. +Do not use `orca` / `orca-ide` when plain shell tools are simpler and Orca state does not matter. Examples: @@ -42,27 +46,30 @@ Examples: ## Preconditions -- Prefer the public `orca` command first +- Prefer the public `orca` command first (`orca-ide` on Linux) - Orca editor/runtime should already be running, or the agent should start it with `orca open` -- Do not begin by inspecting Orca source files just to decide how to invoke the CLI. The first step is to check whether the installed `orca` command exists. +- Do not begin by inspecting Orca source files just to decide how to invoke the CLI. The first step is to check whether the installed `orca` / `orca-ide` command exists. - Do not assume a generic shell environment variable proves the agent is "inside Orca". For normal agent flows, the public CLI is the supported surface, but avoid wasting a round trip on probe-only checks when a direct Orca action would answer the question. First verify the public CLI is installed: ```bash +# macOS / Windows command -v orca +# Linux +command -v orca-ide ``` Then use the public command: ```bash -orca status --json +orca status --json # or orca-ide on Linux ``` If the task is about Orca worktrees or Orca terminals, do this before any codebase exploration: ```bash -command -v orca +command -v orca # or orca-ide on Linux orca status --json ``` @@ -72,7 +79,7 @@ If the agent truly needs to confirm that the current directory is inside an Orca orca worktree current --json ``` -If `orca` is not on PATH, say so explicitly and stop or ask the user to install/register the CLI before continuing. +If `orca` / `orca-ide` is not on PATH, say so explicitly and stop or ask the user to install/register the CLI before continuing. ## Core Workflow @@ -237,7 +244,7 @@ Why: `--direction horizontal` splits the pane **left and right** (new pane appea - Treat `orca worktree set --worktree active --comment ... --json` as a default coding-agent behavior whenever the agent reaches a meaningful checkpoint in the current Orca-managed worktree; the user does not need to explicitly ask for each update. - Update the worktree comment at significant checkpoints, not every trivial command. Good checkpoints include reproducing a bug, confirming a hypothesis, starting a risky migration, finishing a meaningful implementation slice, switching from investigation to fix, or blocking on external input. - Write comments as short status snapshots of the current state, for example `debugging AWS CLI profile resolution`, `confirmed flaky test is caused by temp-dir race`, or `fix implemented; running integration tests`. -- Prefer optimistic execution over probe-first flows for checkpoint updates: if `orca` is on `PATH`, call `orca worktree set --worktree active --comment ... --json` directly at the checkpoint instead of spending an extra cycle on `orca worktree current`. +- Prefer optimistic execution over probe-first flows for checkpoint updates: if `orca` (or `orca-ide` on Linux) is on `PATH`, call `orca worktree set --worktree active --comment ... --json` directly at the checkpoint instead of spending an extra cycle on `orca worktree current`. - If that direct update fails because Orca is unavailable or the shell is not inside an Orca-managed worktree, continue the main task and treat the comment update as best-effort unless the user explicitly made Orca state part of the task. - Use `orca worktree current --json` only when the agent actually needs the worktree identity for later logic, not as a preflight before every comment update. - Orca only injects `ORCA_WORKTREE_PATH`-style variables for some setup-hook flows, so they are not a general detection contract for agents. @@ -249,12 +256,12 @@ Why: `--direction horizontal` splits the pane **left and right** (new pane appea - Use `terminal create` to spin up new terminal tabs programmatically, optionally with a `--command` for startup (e.g. `--command "claude"` to launch Claude Code) and `--title` for labeling. In local Orca sessions, `--command "codex"` is routed through Orca's visible terminal path automatically so Codex does not start as a headless/background PTY. After creating a `--command` terminal, use `terminal wait --for tui-idle` to wait for the agent to boot before dispatching. - Use `terminal split` to create split panes within an existing terminal tab. Pass `--command` to run a command in the new pane. - Prefer Orca worktree selectors over hardcoded paths when Orca identity already exists. -- If the user asks for CLI UX feedback, test the public `orca` command first. Only inspect `src/cli` or use `node out/cli/index.js` if the public command is missing or the task is explicitly about implementation internals. -- If a command fails, prefer retrying with the public `orca` command before concluding the CLI is broken, unless the failure already came from `orca` itself. +- If the user asks for CLI UX feedback, test the public `orca` / `orca-ide` command first. Only inspect `src/cli` or use `node out/cli/index.js` if the public command is missing or the task is explicitly about implementation internals. +- If a command fails, prefer retrying with the public `orca` / `orca-ide` command before concluding the CLI is broken, unless the failure already came from the CLI itself. ## Browser Automation -The `orca` CLI also drives the built-in Orca browser. The core workflow is a **snapshot-interact-re-snapshot** loop: +The `orca` CLI (or `orca-ide` on Linux) also drives the built-in Orca browser. The core workflow is a **snapshot-interact-re-snapshot** loop: 1. **Snapshot** the page to see interactive elements and their refs. 2. **Interact** using refs (`@e1`, `@e3`, etc.) to click, fill, or select. @@ -626,7 +633,7 @@ When `orca tab create` opens a new tab, it is automatically set as the active ta - Terminal handles are ephemeral and tied to the current Orca runtime. If Orca restarts, handles change. - `terminal wait` supports `--for exit` (wait for process exit) and `--for tui-idle` (wait for a recognized agent CLI like Claude Code, Gemini, or Codex to finish its current task, detected via OSC title transitions). `tui-idle` defaults to a 5-minute timeout if `--timeout-ms` is not specified. Real coding tasks routinely take 15-60 minutes — always pass `--timeout-ms` explicitly. - Orca is the source of truth for worktree/terminal state; do not duplicate that state with manual assumptions. -- The public `orca` command is the interface users experience. Agents should validate and use that surface, not repo-local implementation entrypoints. +- The public `orca` command (`orca-ide` on Linux) is the interface users experience. Agents should validate and use that surface, not repo-local implementation entrypoints. - The default bounded `terminal read` preview is for status monitoring. For retained transcript extraction, use `terminal read --json` with `oldestCursor`/`nextCursor`, `--cursor`, and `--limit`. ## References diff --git a/skills/orchestration/SKILL.md b/skills/orchestration/SKILL.md index 86e682922..9a22023be 100644 --- a/skills/orchestration/SKILL.md +++ b/skills/orchestration/SKILL.md @@ -28,7 +28,7 @@ Use `orca-cli` instead for ordinary terminal control, shell commands, browser au ## Preconditions - Orca must be running (`orca status --json` should return `runtime: true`). -- The `orca` CLI must be on PATH (installed via Settings > Browser > Enable Orca CLI). +- The `orca` CLI must be on PATH (`orca-ide` on Linux; installed via Settings > Browser > Enable Orca CLI). - The orchestration experimental feature must be enabled in Settings > Experimental. - All `orca orchestration` commands are RPC calls to the running Orca runtime — they require an active Orca session. @@ -92,7 +92,7 @@ orca orchestration dispatch --task --to [--from ] [-- orca orchestration dispatch-show --task [--json] ``` -Why: `--inject` sends a preamble that teaches the agent how to use `orca orchestration send --type worker_done` to report completion. All agents have `orca` on PATH and can execute shell commands. The preamble maximizes structured feedback but the system works without it (coordinator falls back to idle detection + output reading). +Why: `--inject` sends a preamble that teaches the agent how to use `orca orchestration send --type worker_done` to report completion. All agents have `orca` (or `orca-ide` on Linux) on PATH and can execute shell commands. The preamble maximizes structured feedback but the system works without it (coordinator falls back to idle detection + output reading). Why: `--inject` requires a recognized agent CLI (e.g. Claude Code) running in the target terminal. If the terminal is a bare shell, omit `--inject` and send the prompt manually with `terminal send`. diff --git a/src/main/cli/cli-installer.test.ts b/src/main/cli/cli-installer.test.ts index b0dc2eb24..43f525b3f 100644 --- a/src/main/cli/cli-installer.test.ts +++ b/src/main/cli/cli-installer.test.ts @@ -1,4 +1,4 @@ -import { mkdtemp, mkdir, readFile, symlink, writeFile } from 'node:fs/promises' +import { lstat, mkdtemp, mkdir, readFile, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' @@ -33,60 +33,98 @@ describe('CliInstaller', () => { }) // Why: this test creates Unix symlinks and shell scripts that only apply on macOS. - it.skipIf(process.platform === 'win32')('creates a dev launcher and installs a macOS symlink in the requested path', async () => { - const fixture = await makeFixture() - const installPath = join(fixture.root, 'bin', 'orca') - const installer = new CliInstaller({ - platform: 'darwin', - isPackaged: false, - userDataPath: fixture.userDataPath, - execPath: '/Applications/Orca.app/Contents/MacOS/Orca', - appPath: fixture.appPath, - commandPathOverride: installPath, - processPathEnv: join(fixture.root, 'bin') - }) + it.skipIf(process.platform === 'win32')( + 'creates a dev launcher and installs a macOS symlink in the requested path', + async () => { + const fixture = await makeFixture() + const installPath = join(fixture.root, 'bin', 'orca') + const installer = new CliInstaller({ + platform: 'darwin', + isPackaged: false, + userDataPath: fixture.userDataPath, + execPath: '/Applications/Orca.app/Contents/MacOS/Orca', + appPath: fixture.appPath, + commandPathOverride: installPath, + processPathEnv: join(fixture.root, 'bin') + }) - const initial = await installer.getStatus() - expect(initial.state).toBe('not_installed') - expect(initial.launcherPath).toContain(join('userData', 'cli', 'bin', 'orca')) + const initial = await installer.getStatus() + expect(initial.state).toBe('not_installed') + expect(initial.launcherPath).toContain(join('userData', 'cli', 'bin', 'orca')) - const installed = await installer.install() - expect(installed.state).toBe('installed') - expect(installed.pathConfigured).toBe(true) + const installed = await installer.install() + expect(installed.state).toBe('installed') + expect(installed.pathConfigured).toBe(true) - const launcherContent = await readFile(installed.launcherPath as string, 'utf8') - expect(launcherContent).toContain('ELECTRON_RUN_AS_NODE=1') - expect(launcherContent).toContain(join(fixture.appPath, 'out', 'cli', 'index.js')) + const launcherContent = await readFile(installed.launcherPath as string, 'utf8') + expect(launcherContent).toContain('ELECTRON_RUN_AS_NODE=1') + expect(launcherContent).toContain(join(fixture.appPath, 'out', 'cli', 'index.js')) - const removed = await installer.remove() - expect(removed.state).toBe('not_installed') - }) + const removed = await installer.remove() + expect(removed.state).toBe('not_installed') + } + ) // Why: this test creates Unix symlinks and shell scripts that only apply on Linux. - it.skipIf(process.platform === 'win32')('creates a linux symlink under the requested path and warns when PATH is missing', async () => { - const fixture = await makeFixture() - const installPath = join(fixture.root, '.local', 'bin', 'orca') - const installer = new CliInstaller({ - platform: 'linux', - isPackaged: false, - userDataPath: fixture.userDataPath, - execPath: '/opt/Orca/orca', - appPath: fixture.appPath, - commandPathOverride: installPath, - processPathEnv: '/usr/bin' - }) + it.skipIf(process.platform === 'win32')( + 'creates a linux symlink under the requested path and warns when PATH is missing', + async () => { + const fixture = await makeFixture() + const installPath = join(fixture.root, '.local', 'bin', 'orca-ide') + const installer = new CliInstaller({ + platform: 'linux', + isPackaged: false, + userDataPath: fixture.userDataPath, + execPath: '/opt/Orca/orca-ide', + appPath: fixture.appPath, + commandPathOverride: installPath, + processPathEnv: '/usr/bin' + }) - const installed = await installer.install() - expect(installed.state).toBe('installed') - expect(installed.pathConfigured).toBe(false) - expect(installed.detail).toContain('.local') + const installed = await installer.install() + expect(installed.state).toBe('installed') + expect(installed.commandName).toBe('orca-ide') + expect(installed.pathConfigured).toBe(false) + expect(installed.detail).toContain('.local') - const launcherContent = await readFile(installed.launcherPath as string, 'utf8') - expect(launcherContent).toContain('ELECTRON_RUN_AS_NODE=1') + const launcherContent = await readFile(installed.launcherPath as string, 'utf8') + expect(launcherContent).toContain('ELECTRON_RUN_AS_NODE=1') - const removed = await installer.remove() - expect(removed.state).toBe('not_installed') - }) + const removed = await installer.remove() + expect(removed.state).toBe('not_installed') + } + ) + + // Why: Linux renamed the public command to avoid shadowing GNOME Orca, so + // upgrading must clean up only the old symlink owned by prior Orca installs. + it.skipIf(process.platform === 'win32')( + 'removes the old managed linux orca symlink when installing orca-ide', + async () => { + const fixture = await makeFixture() + const homePath = join(fixture.root, 'home') + const commandDir = join(homePath, '.local', 'bin') + const oldLauncherPath = join(fixture.userDataPath, 'cli', 'bin', 'orca') + const legacyCommandPath = join(commandDir, 'orca') + await mkdir(commandDir, { recursive: true }) + await mkdir(join(fixture.userDataPath, 'cli', 'bin'), { recursive: true }) + await writeFile(oldLauncherPath, '#!/usr/bin/env bash\n', 'utf8') + await symlink(oldLauncherPath, legacyCommandPath) + + const installer = new CliInstaller({ + platform: 'linux', + isPackaged: false, + userDataPath: fixture.userDataPath, + execPath: '/opt/Orca/orca-ide', + appPath: fixture.appPath, + homePath, + processPathEnv: commandDir + }) + + const installed = await installer.install() + expect(installed.commandPath).toBe(join(commandDir, 'orca-ide')) + await expect(lstat(legacyCommandPath)).rejects.toMatchObject({ code: 'ENOENT' }) + } + ) it('creates a windows wrapper and updates the user PATH', async () => { const fixture = await makeFixture() @@ -120,24 +158,27 @@ describe('CliInstaller', () => { }) // Why: this test creates a Unix symlink to /tmp/not-orca, which only applies on macOS/Linux. - it.skipIf(process.platform === 'win32')('reports stale when a different symlink already exists', async () => { - const fixture = await makeFixture() - const installPath = join(fixture.root, 'bin', 'orca') - await mkdir(join(fixture.root, 'bin'), { recursive: true }) - await symlink('/tmp/not-orca', installPath) + it.skipIf(process.platform === 'win32')( + 'reports stale when a different symlink already exists', + async () => { + const fixture = await makeFixture() + const installPath = join(fixture.root, 'bin', 'orca') + await mkdir(join(fixture.root, 'bin'), { recursive: true }) + await symlink('/tmp/not-orca', installPath) - const installer = new CliInstaller({ - platform: 'darwin', - isPackaged: false, - userDataPath: fixture.userDataPath, - execPath: '/Applications/Orca.app/Contents/MacOS/Orca', - appPath: fixture.appPath, - commandPathOverride: installPath - }) + const installer = new CliInstaller({ + platform: 'darwin', + isPackaged: false, + userDataPath: fixture.userDataPath, + execPath: '/Applications/Orca.app/Contents/MacOS/Orca', + appPath: fixture.appPath, + commandPathOverride: installPath + }) - await expect(installer.getStatus()).resolves.toMatchObject({ - state: 'stale', - supported: true - }) - }) + await expect(installer.getStatus()).resolves.toMatchObject({ + state: 'stale', + supported: true + }) + } + ) }) diff --git a/src/main/cli/cli-installer.ts b/src/main/cli/cli-installer.ts index a3dde42ac..054517b37 100644 --- a/src/main/cli/cli-installer.ts +++ b/src/main/cli/cli-installer.ts @@ -10,6 +10,8 @@ import type { CliInstallMethod, CliInstallStatus } from '../../shared/cli-instal const execFileAsync = promisify(execFile) const DEFAULT_MAC_COMMAND_PATH = '/usr/local/bin/orca' +const LINUX_COMMAND_NAME = 'orca-ide' +const LEGACY_LINUX_COMMAND_NAME = 'orca' const DEV_LAUNCHER_DIR = ['cli', 'bin'] type CliInstallerOptions = { @@ -48,6 +50,11 @@ export class CliInstaller { private readonly userPathReader: () => Promise private readonly userPathWriter: (value: string) => Promise + // Why: Linux uses `orca-ide` to avoid shadowing GNOME Orca's /usr/bin/orca. + private get commandName(): string { + return this.platform === 'linux' ? LINUX_COMMAND_NAME : 'orca' + } + constructor(options: CliInstallerOptions = {}) { this.platform = options.platform ?? process.platform this.isPackaged = options.isPackaged ?? app.isPackaged @@ -73,7 +80,7 @@ export class CliInstaller { if (!spec) { return { platform: this.platform, - commandName: 'orca', + commandName: this.commandName, commandPath: null, pathDirectory: null, pathConfigured: false, @@ -91,7 +98,7 @@ export class CliInstaller { if (!launcherPath) { return { platform: this.platform, - commandName: 'orca', + commandName: this.commandName, commandPath: spec.commandPath, pathDirectory: dirname(spec.commandPath), pathConfigured: false, @@ -130,6 +137,7 @@ export class CliInstaller { // eslint-disable-next-line unicorn/prefer-ternary -- Why: the install path performs async side effects and is easier to audit as an explicit branch than as an awaited ternary. if (status.installMethod === 'symlink') { await this.installSymlink(status) + await this.removeLegacyLinuxCommandIfManaged(status.launcherPath) } else { await this.installWindowsWrapper(status.commandPath, status.launcherPath) } @@ -150,6 +158,7 @@ export class CliInstaller { return status } if (status.state === 'not_installed') { + await this.removeLegacyLinuxCommandIfManaged(status.launcherPath) if (this.platform === 'win32') { await this.removeWindowsPathEntry(dirname(status.commandPath)) return this.getStatus() @@ -165,6 +174,7 @@ export class CliInstaller { if (status.installMethod === 'symlink') { await this.removeSymlink(status.commandPath) + await this.removeLegacyLinuxCommandIfManaged(status.launcherPath) } else { await unlink(status.commandPath) await this.removeWindowsPathEntry(dirname(status.commandPath)) @@ -209,7 +219,10 @@ export class CliInstaller { // Why: Linux does not have a single privileged global shell-command flow // equivalent to macOS's /usr/local/bin integration. ~/.local/bin is the // least surprising user-scoped location that many distros already expose. - return join(this.homePath, '.local', 'bin', 'orca') + // Why `orca-ide`: GNOME Orca (the screen reader) ships /usr/bin/orca on + // most Linux distros. Using `orca-ide` avoids shadowing that system + // command, matching the executableName already used for the Electron binary. + return join(this.homePath, '.local', 'bin', LINUX_COMMAND_NAME) } if (this.platform === 'win32') { @@ -275,6 +288,36 @@ export class CliInstaller { } } + private async removeLegacyLinuxCommandIfManaged(launcherPath: string | null): Promise { + if (this.platform !== 'linux' || this.commandPathOverride || !launcherPath) { + return + } + + const legacyCommandPath = join(this.homePath, '.local', 'bin', LEGACY_LINUX_COMMAND_NAME) + try { + const stats = await lstat(legacyCommandPath) + if (!stats.isSymbolicLink()) { + return + } + + const currentTarget = await readlink(legacyCommandPath) + const resolvedCurrentTarget = resolve(dirname(legacyCommandPath), currentTarget) + const legacyLauncherPath = resolve(dirname(launcherPath), LEGACY_LINUX_COMMAND_NAME) + if (resolvedCurrentTarget !== legacyLauncherPath) { + return + } + + // Why: after the Linux command rename, the old Orca-owned `orca` symlink + // would keep shadowing GNOME Orca even though the new command is installed. + await unlink(legacyCommandPath) + } catch (error) { + if (isMissingError(error)) { + return + } + throw error + } + } + private async installWindowsWrapper(commandPath: string, launcherPath: string): Promise { await writeFile(commandPath, buildWindowsForwarder(launcherPath), 'utf8') } @@ -387,7 +430,7 @@ export class CliInstaller { }): CliInstallStatus { return { platform: this.platform, - commandName: 'orca', + commandName: this.commandName, commandPath: args.commandPath, pathDirectory: dirname(args.commandPath), pathConfigured: false, @@ -480,7 +523,7 @@ async function ensureDevLauncher(args: { const launcherPath = join( args.userDataPath, ...DEV_LAUNCHER_DIR, - args.platform === 'win32' ? 'orca.cmd' : 'orca' + args.platform === 'win32' ? 'orca.cmd' : args.platform === 'linux' ? LINUX_COMMAND_NAME : 'orca' ) await mkdir(dirname(launcherPath), { recursive: true }) @@ -623,9 +666,12 @@ export function getBundledLauncherPath( platform: NodeJS.Platform, resourcesPath: string ): string | null { - if (platform === 'darwin' || platform === 'linux') { + if (platform === 'darwin') { return join(resourcesPath, 'bin', 'orca') } + if (platform === 'linux') { + return join(resourcesPath, 'bin', LINUX_COMMAND_NAME) + } if (platform === 'win32') { return join(resourcesPath, 'bin', 'orca.cmd') } diff --git a/src/main/cli/packaged-cli-assets.test.ts b/src/main/cli/packaged-cli-assets.test.ts index fcf877af8..b13d794f9 100644 --- a/src/main/cli/packaged-cli-assets.test.ts +++ b/src/main/cli/packaged-cli-assets.test.ts @@ -1,5 +1,5 @@ import { execFile } from 'node:child_process' -import { copyFile, chmod, mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' +import { copyFile, chmod, mkdir, mkdtemp, rm, stat, symlink, writeFile } from 'node:fs/promises' import { createRequire } from 'node:module' import { tmpdir } from 'node:os' import { join } from 'node:path' @@ -12,7 +12,7 @@ const itRunsUnixShell = process.platform === 'win32' ? it.skip : it const builderConfig = require('../../../config/electron-builder.config.cjs') as { asarUnpack?: string[] } -const linuxLauncherAsset = new URL('../../../resources/linux/bin/orca', import.meta.url) +const linuxLauncherAsset = new URL('../../../resources/linux/bin/orca-ide', import.meta.url) describe('packaged CLI assets', () => { it('unpacks runtime dependencies used before Electron asar integration is available', () => { @@ -26,6 +26,11 @@ describe('packaged CLI assets', () => { ) }) + itRunsUnixShell('keeps the Linux launcher executable in packaged resources', async () => { + const launcherStats = await stat(linuxLauncherAsset) + expect(launcherStats.mode & 0o111).not.toBe(0) + }) + itRunsUnixShell( 'runs the Linux launcher from its packaged path and installed symlink', async () => { @@ -35,7 +40,7 @@ describe('packaged CLI assets', () => { const resourcesDir = join(appDir, 'resources') const launcherDir = join(resourcesDir, 'bin') const cliDir = join(resourcesDir, 'app.asar.unpacked', 'out', 'cli') - const launcherPath = join(launcherDir, 'orca') + const launcherPath = join(launcherDir, 'orca-ide') const electronPath = join(appDir, 'orca-ide') const cliPath = join(cliDir, 'index.js') @@ -62,7 +67,7 @@ printf 'arg=%s\\n' "$@" const homeDir = join(root, 'home') const commandDir = join(homeDir, '.local', 'bin') - const commandPath = join(commandDir, 'orca') + const commandPath = join(commandDir, 'orca-ide') await mkdir(commandDir, { recursive: true }) await mkdir(join(homeDir, 'orca'), { recursive: true }) await symlink(launcherPath, commandPath) diff --git a/src/main/cli/wsl-cli-installer.test.ts b/src/main/cli/wsl-cli-installer.test.ts index ef51b67df..3804251c1 100644 --- a/src/main/cli/wsl-cli-installer.test.ts +++ b/src/main/cli/wsl-cli-installer.test.ts @@ -20,7 +20,7 @@ function makeHostStatus(launcherPath = 'C:\\Users\\me\\AppData\\Local\\Orca\\bin } function createWslRunner(initialFile: string | null = null, pathIncludesLocalBin = true) { - const commandPath = '/home/alice/.local/bin/orca' + const commandPath = '/home/alice/.local/bin/orca-ide' const bridgePath = '/home/alice/.local/share/orca/orca-wsl-bridge.ps1' const files = new Map() if (initialFile !== null) { @@ -91,7 +91,7 @@ describe('WslCliInstaller', () => { await expect(installer.getStatus()).resolves.toMatchObject({ state: 'not_installed', - commandPath: '/home/alice/.local/bin/orca' + commandPath: '/home/alice/.local/bin/orca-ide' }) const installed = await installer.install() @@ -108,6 +108,18 @@ describe('WslCliInstaller', () => { ) ) expect(wsl.getBridge()).toBe(_internals.buildWslBridgeScript()) + const installCommand = wsl.calls.find((command) => command.includes('cat > "$command_tmp"')) + expect(installCommand).toContain("legacy_command_path='/home/alice/.local/bin/orca'") + expect(installCommand).toContain('rm -f "$legacy_command_path"') + }) + + it('derives the shared WSL bridge path for current and legacy command names', () => { + expect(_internals.getBridgePathFromCommandPath('/home/alice/.local/bin/orca-ide')).toBe( + '/home/alice/.local/share/orca/orca-wsl-bridge.ps1' + ) + expect(_internals.getBridgePathFromCommandPath('/home/alice/.local/bin/orca')).toBe( + '/home/alice/.local/share/orca/orca-wsl-bridge.ps1' + ) }) it('reports installed WSL launchers whose bin directory is missing from PATH', async () => { diff --git a/src/main/cli/wsl-cli-installer.ts b/src/main/cli/wsl-cli-installer.ts index 257ea5db3..33eedeb3e 100644 --- a/src/main/cli/wsl-cli-installer.ts +++ b/src/main/cli/wsl-cli-installer.ts @@ -21,6 +21,8 @@ import { const execFileAsync = promisify(execFile) const MANAGED_MARKER = getWslLauncherMarker() const BRIDGE_MANAGED_MARKER = getWslBridgeMarker() +const WSL_COMMAND_NAME = 'orca-ide' +const LEGACY_WSL_COMMAND_NAME = 'orca' type WslCliInstallerOptions = { platform?: NodeJS.Platform @@ -137,6 +139,9 @@ export class WslCliInstaller { `mkdir -p ${quoteShell(getPosixDirname(getBridgePathFromCommandPath(status.commandPath)))}`, `command_tmp=${quoteShell(`${status.commandPath}.tmp`)}.$$`, `bridge_path=${quoteShell(getBridgePathFromCommandPath(status.commandPath))}`, + `legacy_command_path=${quoteShell( + `${getPosixDirname(status.commandPath)}/${LEGACY_WSL_COMMAND_NAME}` + )}`, 'bridge_tmp="${bridge_path}.tmp.$$"', 'cleanup() { rm -f "$command_tmp" "$bridge_tmp"; }', 'trap cleanup EXIT', @@ -158,6 +163,9 @@ export class WslCliInstaller { getBridgePathFromCommandPath(status.commandPath), BRIDGE_MANAGED_MARKER ), + // Why: the command was renamed to avoid GNOME Orca; remove only the + // old Orca-managed WSL wrapper so unmanaged `orca` commands survive. + `if [ -f "$legacy_command_path" ] && grep -Fq ${quoteShell(MANAGED_MARKER)} "$legacy_command_path"; then rm -f "$legacy_command_path"; fi`, `mv -f "$bridge_tmp" ${quoteShell(getBridgePathFromCommandPath(status.commandPath))}`, `mv -f "$command_tmp" ${quoteShell(status.commandPath)}`, 'trap - EXIT' @@ -240,7 +248,8 @@ export class WslCliInstaller { } const pathDirectory = `${home}/.local/bin` - const commandPath = `${pathDirectory}/orca` + // Why: matches the Linux CLI rename to `orca-ide` (avoids GNOME Orca conflict). + const commandPath = `${pathDirectory}/${WSL_COMMAND_NAME}` const pathConfigured = ( await this.run( @@ -296,9 +305,9 @@ export class WslCliInstaller { }): CliInstallStatus { return { platform: 'linux', - commandName: 'orca', + commandName: WSL_COMMAND_NAME, commandPath: args.commandPath, - pathDirectory: args.commandPath.replace(/\/orca$/, ''), + pathDirectory: getPosixDirname(args.commandPath), pathConfigured: args.pathConfigured, launcherPath: args.launcherPath, installMethod: 'wrapper', @@ -308,7 +317,7 @@ export class WslCliInstaller { unsupportedReason: null, detail: args.state === 'installed' && !args.pathConfigured - ? `${args.commandPath} is registered, but ${args.commandPath.replace(/\/orca$/, '')} is not on PATH in ${args.distro}.` + ? `${args.commandPath} is registered, but ${getPosixDirname(args.commandPath)} is not on PATH in ${args.distro}.` : args.detail } } @@ -319,7 +328,7 @@ export class WslCliInstaller { ): CliInstallStatus { return { platform: 'linux', - commandName: 'orca', + commandName: WSL_COMMAND_NAME, commandPath: null, pathDirectory: null, pathConfigured: false, @@ -360,5 +369,6 @@ function buildEncodedWslBashCommand(command: string): string { export const _internals = { buildEncodedWslBashCommand, buildWslBridgeScript, - buildWslLauncher + buildWslLauncher, + getBridgePathFromCommandPath } diff --git a/src/main/cli/wsl-cli-scripts.ts b/src/main/cli/wsl-cli-scripts.ts index 0a74e9c51..23f8f27ce 100644 --- a/src/main/cli/wsl-cli-scripts.ts +++ b/src/main/cli/wsl-cli-scripts.ts @@ -44,7 +44,9 @@ try { } export function getBridgePathFromCommandPath(commandPath: string): string { - return `${commandPath.replace(/\/\.local\/bin\/orca$/, '/.local/share/orca')}/orca-wsl-bridge.ps1` + // Why: both the current Linux command and the legacy pre-rename command + // share one WSL bridge under ~/.local/share/orca. + return `${commandPath.replace(/\/\.local\/bin\/(?:orca|orca-ide)$/, '/.local/share/orca')}/orca-wsl-bridge.ps1` } export function buildSafeReplaceGuard(path: string, managedMarker: string): string { diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx index 7a6d4e17b..6e4d0044a 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalOrchestrationDialog.tsx @@ -61,10 +61,10 @@ export function FloatingTerminalOrchestrationDialog({ const cliInstalled = isOrcaCliAvailableOnPath(cliStatus) const cliSupported = cliStatus?.supported ?? false const cliLabel = cliInstalled - ? 'orca is on PATH' + ? 'Orca CLI is on PATH' : cliLoading ? 'Checking CLI status...' - : (cliStatus?.detail ?? 'Register orca so agents can call Orca from a terminal.') + : (cliStatus?.detail ?? 'Register the Orca CLI so agents can call Orca from a terminal.') const handleInstallCli = async (): Promise => { setCliBusy(true) @@ -77,7 +77,7 @@ export function FloatingTerminalOrchestrationDialog({ onSetupStateChange() } if (isOrcaCliAvailableOnPath(next)) { - toast.success('Registered `orca` in PATH.') + toast.success('Registered the Orca CLI in PATH.') } } finally { setCliBusy(false) diff --git a/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx b/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx index 88a6a0e16..7d07a45bd 100644 --- a/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx +++ b/src/renderer/src/components/onboarding/FeatureSetupChecklist.tsx @@ -31,14 +31,14 @@ const FEATURE_SETUP_ROWS: readonly FeatureSetupRow[] = [ id: 'computerUse', title: 'Computer Use', description: 'Agents can inspect app windows and operate local apps when you ask.', - setupSummary: 'Registers `orca`, opens permissions, and prepares the skill.', + setupSummary: 'Registers the Orca CLI, opens permissions, and prepares the skill.', icon: }, { id: 'orchestration', title: 'Agent Orchestration', description: 'Agents can message each other, take tasks, and coordinate handoffs.', - setupSummary: 'Registers `orca`, enables orchestration, and prepares the skill.', + setupSummary: 'Registers the Orca CLI, enables orchestration, and prepares the skill.', icon: } ] diff --git a/src/renderer/src/components/settings/BrowserUsePane.tsx b/src/renderer/src/components/settings/BrowserUsePane.tsx index f2677f529..146a6260c 100644 --- a/src/renderer/src/components/settings/BrowserUsePane.tsx +++ b/src/renderer/src/components/settings/BrowserUsePane.tsx @@ -124,7 +124,7 @@ export function BrowserUseSetup({ onStatusChange: setCliStatus }) if (isOrcaCliAvailableOnPath(next)) { - toast.success('Registered `orca` in PATH.') + toast.success('Registered the Orca CLI in PATH.') } } finally { setCliBusy(false) @@ -254,7 +254,7 @@ export function BrowserUseSetup({ {showStep1 ? ( @@ -266,8 +266,8 @@ export function BrowserUseSetup({

Enable Orca CLI

- Registers the orca{' '} - command so agents can orchestrate the browser from their shell. + Registers the Orca CLI command so agents can orchestrate the browser from their + shell.

{cliStatus?.commandPath && cliEnabled ? (

diff --git a/src/renderer/src/components/settings/CliSection.tsx b/src/renderer/src/components/settings/CliSection.tsx index bc332e736..71012697b 100644 --- a/src/renderer/src/components/settings/CliSection.tsx +++ b/src/renderer/src/components/settings/CliSection.tsx @@ -47,7 +47,7 @@ function getInstallDescription(platform: string): string { return 'Register `orca` in /usr/local/bin.' } if (platform === 'linux') { - return 'Register `orca` in ~/.local/bin.' + return 'Register `orca-ide` in ~/.local/bin.' } if (platform === 'win32') { return 'Register `orca` in your user PATH.' @@ -55,6 +55,10 @@ function getInstallDescription(platform: string): string { return 'CLI registration is not yet available on this platform.' } +function getFallbackCommandName(platform: string): string { + return platform === 'linux' ? 'orca-ide' : 'orca' +} + export function CliSection({ currentPlatform }: CliSectionProps): React.JSX.Element { const [status, setStatus] = useState(null) const [loading, setLoading] = useState(true) @@ -88,6 +92,7 @@ export function CliSection({ currentPlatform }: CliSectionProps): React.JSX.Elem const isSupported = status?.supported ?? false const isBrowserManaged = status?.unsupportedReason === 'launch_mode_unavailable' const revealLabel = getRevealLabel(currentPlatform) + const commandName = status?.commandName ?? getFallbackCommandName(currentPlatform) const canRevealCommandPath = status?.commandPath != null && ['installed', 'stale', 'conflict'].includes(status.state) @@ -97,9 +102,11 @@ export function CliSection({ currentPlatform }: CliSectionProps): React.JSX.Elem const next = await window.api.cli.install() setStatus(next) setDialogOpen(false) - toast.success('Registered `orca` in PATH.') + toast.success(`Registered \`${next.commandName}\` in PATH.`) } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to register `orca` in PATH.') + toast.error( + error instanceof Error ? error.message : `Failed to register \`${commandName}\` in PATH.` + ) } finally { setBusyAction(null) } @@ -111,9 +118,11 @@ export function CliSection({ currentPlatform }: CliSectionProps): React.JSX.Elem const next = await window.api.cli.remove() setStatus(next) setDialogOpen(false) - toast.success('Removed `orca` from PATH.') + toast.success(`Removed \`${next.commandName}\` from PATH.`) } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to remove `orca` from PATH.') + toast.error( + error instanceof Error ? error.message : `Failed to remove \`${commandName}\` from PATH.` + ) } finally { setBusyAction(null) } @@ -253,12 +262,14 @@ export function CliSection({ currentPlatform }: CliSectionProps): React.JSX.Elem - {isEnabled ? 'Remove `orca` from PATH?' : 'Register `orca` in PATH?'} + {isEnabled + ? `Remove \`${commandName}\` from PATH?` + : `Register \`${commandName}\` in PATH?`} {isEnabled ? 'This removes the shell command symlink. Orca itself remains installed.' - : `Orca will register ${status?.commandPath ?? '`orca`'} so the command works from your terminal.`} + : `Orca will register ${status?.commandPath ?? commandName} so the command works from your terminal.`} {status?.commandPath ? ( diff --git a/src/renderer/src/components/settings/WslCliRegistration.tsx b/src/renderer/src/components/settings/WslCliRegistration.tsx index 0450c5a5a..c1d9f1eda 100644 --- a/src/renderer/src/components/settings/WslCliRegistration.tsx +++ b/src/renderer/src/components/settings/WslCliRegistration.tsx @@ -52,6 +52,7 @@ export function WslCliRegistration({ const isEnabled = status?.state === 'installed' const isSupported = status?.supported ?? false + const commandName = status?.commandName ?? 'orca-ide' const handleInstall = async (): Promise => { setBusyAction('install') @@ -59,9 +60,11 @@ export function WslCliRegistration({ const next = await window.api.cli.installWsl() setStatus(next) setDialogOpen(false) - toast.success('Registered `orca` in WSL.') + toast.success(`Registered \`${next.commandName}\` in WSL.`) } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to register `orca` in WSL.') + toast.error( + error instanceof Error ? error.message : `Failed to register \`${commandName}\` in WSL.` + ) } finally { setBusyAction(null) } @@ -73,9 +76,11 @@ export function WslCliRegistration({ const next = await window.api.cli.removeWsl() setStatus(next) setDialogOpen(false) - toast.success('Removed `orca` from WSL.') + toast.success(`Removed \`${next.commandName}\` from WSL.`) } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to remove `orca` from WSL.') + toast.error( + error instanceof Error ? error.message : `Failed to remove \`${commandName}\` from WSL.` + ) } finally { setBusyAction(null) } @@ -90,7 +95,7 @@ export function WslCliRegistration({

{loading ? 'Checking WSL CLI registration...' - : (status?.detail ?? 'Register `orca` in ~/.local/bin inside WSL.')} + : (status?.detail ?? 'Register `orca-ide` in ~/.local/bin inside WSL.')}

@@ -148,12 +153,14 @@ export function WslCliRegistration({ - {isEnabled ? 'Remove `orca` from WSL?' : 'Register `orca` in WSL?'} + {isEnabled + ? `Remove \`${commandName}\` from WSL?` + : `Register \`${commandName}\` in WSL?`} {isEnabled ? 'This removes the WSL shell command. Orca itself remains installed on Windows.' - : `Orca will register ${status?.commandPath ?? '`orca`'} so the command works from WSL terminals.`} + : `Orca will register ${status?.commandPath ?? commandName} so the command works from WSL terminals.`} {status?.commandPath ? ( diff --git a/src/renderer/src/components/settings/browser-use-search.ts b/src/renderer/src/components/settings/browser-use-search.ts index 84a16c987..6ea9489b4 100644 --- a/src/renderer/src/components/settings/browser-use-search.ts +++ b/src/renderer/src/components/settings/browser-use-search.ts @@ -3,7 +3,7 @@ import type { SettingsSearchEntry } from './settings-search' export const BROWSER_USE_PANE_SEARCH_ENTRIES: SettingsSearchEntry[] = [ { title: 'Enable Orca CLI', - description: 'Register the orca shell command so agents can drive the browser.', + description: 'Register the Orca CLI so agents can drive the browser.', keywords: ['browser use', 'cli', 'orca', 'path', 'command', 'shell', 'enable', 'setup'] }, { diff --git a/src/renderer/src/components/settings/general-search.ts b/src/renderer/src/components/settings/general-search.ts index 332dbd68c..b1d12d4bd 100644 --- a/src/renderer/src/components/settings/general-search.ts +++ b/src/renderer/src/components/settings/general-search.ts @@ -81,14 +81,14 @@ export const GENERAL_NAVIGATION_SEARCH_ENTRIES: SettingsSearchEntry[] = [ export const GENERAL_CLI_SEARCH_ENTRIES: SettingsSearchEntry[] = [ { title: 'Orca CLI', - description: 'Register or remove the orca shell command.', + description: 'Register or remove the Orca CLI command.', keywords: ['cli', 'path', 'terminal', 'command', 'shell command'], cmdJKeywords: ['cli', 'path', 'command', 'shell command'], targetSectionId: 'cli' }, { title: 'Agent skill', - description: 'Install the Orca skill so agents know to use the orca CLI.', + description: 'Install the Orca skill so agents know to use the Orca CLI.', keywords: ['skill', 'agents', 'npx'] } ] diff --git a/src/renderer/src/lib/agent-skill-cli-prerequisite.ts b/src/renderer/src/lib/agent-skill-cli-prerequisite.ts index 5d39018c2..28ad6da2a 100644 --- a/src/renderer/src/lib/agent-skill-cli-prerequisite.ts +++ b/src/renderer/src/lib/agent-skill-cli-prerequisite.ts @@ -7,11 +7,11 @@ type EnsureOrcaCliAvailableOptions = { } export const AGENT_SKILL_CLI_PREREQUISITE_NOTICE = - 'Before opening setup, Orca may show a system prompt to register the orca command on PATH.' + 'Before opening setup, Orca may show a system prompt to register the Orca CLI command on PATH.' export const CLI_PREREQUISITE_REGISTRATION_TOAST = 'Orca needs to register its CLI on PATH.' export const CLI_PREREQUISITE_REGISTRATION_TOAST_DESCRIPTION = - 'Approve the system prompt so skill setup can use the orca command.' + 'Approve the system prompt so skill setup can use the Orca CLI command.' export function isOrcaCliAvailableOnPath(status: CliInstallStatus | null | undefined): boolean { return status?.state === 'installed' && status.pathConfigured @@ -42,7 +42,7 @@ export async function ensureOrcaCliAvailableForAgentSkillTerminal({ return status } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to register `orca` in PATH.') + toast.error(error instanceof Error ? error.message : 'Failed to register the Orca CLI in PATH.') return null } } @@ -78,8 +78,8 @@ function showCliPrerequisiteWarning(status: CliInstallStatus): void { if (!status.pathConfigured) { // Why: the skill installer opens a real shell; agents only get the expected - // Orca affordances when that shell can resolve the `orca` command. - toast.warning('`orca` is not visible on PATH yet', { + // Orca affordances when that shell can resolve the Orca CLI command. + toast.warning('Orca CLI is not visible on PATH yet', { description: status.detail ?? 'Restart your shell or add the Orca CLI directory to PATH before setup.' }) diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index d68eee67d..0dd6e7f48 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -1675,7 +1675,7 @@ function createPreflightApi(): NonNullable['preflight']> { function createCliApi(): NonNullable['cli']> { const status = { platform: getBrowserPlatform(), - commandName: 'orca', + commandName: getBrowserPlatform() === 'linux' ? 'orca-ide' : 'orca', commandPath: null, pathDirectory: null, pathConfigured: false,