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.
This commit is contained in:
parent
80a2b6195b
commit
7337cab2d8
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 <subcommand>`, Linux users should substitute `orca-ide <subcommand>`. 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
|
||||
|
|
|
|||
|
|
@ -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 <task_id> --to <handle> [--from <handle>] [--
|
|||
orca orchestration dispatch-show --task <task_id> [--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`.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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<string | null>
|
||||
private readonly userPathWriter: (value: string) => Promise<void>
|
||||
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
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')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<string, string>()
|
||||
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 () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<void> => {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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: <MonitorCog className="size-4" />
|
||||
},
|
||||
{
|
||||
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: <Workflow className="size-4" />
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
<SearchableSetting
|
||||
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_PANE_SEARCH_ENTRIES[0].keywords}
|
||||
className="rounded-xl border border-border/60 bg-card/50 p-4"
|
||||
>
|
||||
|
|
@ -266,8 +266,8 @@ export function BrowserUseSetup({
|
|||
<div className="min-w-0 flex-1 space-y-1">
|
||||
<p className="text-sm font-medium">Enable Orca CLI</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Registers the <code className="rounded bg-muted px-1 py-0.5 text-[11px]">orca</code>{' '}
|
||||
command so agents can orchestrate the browser from their shell.
|
||||
Registers the Orca CLI command so agents can orchestrate the browser from their
|
||||
shell.
|
||||
</p>
|
||||
{cliStatus?.commandPath && cliEnabled ? (
|
||||
<p className="text-[11px] text-muted-foreground">
|
||||
|
|
|
|||
|
|
@ -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<CliInstallStatus | null>(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
|
|||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isEnabled ? 'Remove `orca` from PATH?' : 'Register `orca` in PATH?'}
|
||||
{isEnabled
|
||||
? `Remove \`${commandName}\` from PATH?`
|
||||
: `Register \`${commandName}\` in PATH?`}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{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.`}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{status?.commandPath ? (
|
||||
|
|
|
|||
|
|
@ -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<void> => {
|
||||
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({
|
|||
<p className="text-xs text-muted-foreground">
|
||||
{loading
|
||||
? 'Checking WSL CLI registration...'
|
||||
: (status?.detail ?? 'Register `orca` in ~/.local/bin inside WSL.')}
|
||||
: (status?.detail ?? 'Register `orca-ide` in ~/.local/bin inside WSL.')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -148,12 +153,14 @@ export function WslCliRegistration({
|
|||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{isEnabled ? 'Remove `orca` from WSL?' : 'Register `orca` in WSL?'}
|
||||
{isEnabled
|
||||
? `Remove \`${commandName}\` from WSL?`
|
||||
: `Register \`${commandName}\` in WSL?`}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{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.`}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
{status?.commandPath ? (
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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.'
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1675,7 +1675,7 @@ function createPreflightApi(): NonNullable<Partial<PreloadApi>['preflight']> {
|
|||
function createCliApi(): NonNullable<Partial<PreloadApi>['cli']> {
|
||||
const status = {
|
||||
platform: getBrowserPlatform(),
|
||||
commandName: 'orca',
|
||||
commandName: getBrowserPlatform() === 'linux' ? 'orca-ide' : 'orca',
|
||||
commandPath: null,
|
||||
pathDirectory: null,
|
||||
pathConfigured: false,
|
||||
|
|
|
|||
Loading…
Reference in New Issue