* refactor(mobile): pin each RPC golden to its own mount adapter, not every domain's `recorderSha256` covered the whole recorder directory, mount adapters included, so a domain PR that adds its adapter module moved the header of all 153 goldens. #20568 did exactly that and its merge with main conflicted on that one line in 153 files; every future domain PR would collide with every other in flight the same way. Split the directory at a real seam instead of a filename convention: `adapters/` holds one module per domain, registered in `adapters/mounted-operation-modules.ts`, and `recorderSha256` now covers the engine only. A new `adapterSha256` covers the source of the module that mounts each operation a golden's scenarios drive, read off the same `mounts` calls that build the table the recording runs against, so the pin cannot name a file the runner did not use. Adding a domain's module now re-digests nothing already recorded; editing one fails exactly the goldens mounted through it. `adapter-seam.test.ts` keeps the split from drifting: an engine file inside `adapters/`, an adapter defined in an engine file, a register entry naming the wrong file, and an adapter importing a sibling each fail. The five adapters that were inline in `pilot-mount-adapters.ts` move into their own modules, which leaves that file as the registry and nothing else. `GOLDEN_FORMAT_VERSION` goes to 5 for the new header field; the goldens re-record in the next commit. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the RPC goldens under the split recorder/adapter digest Header-only. Every changed line is `recorderSha256` (the engine digest no longer covers `adapters/`), the new `adapterSha256`, or `goldenFormatVersion` 4 -> 5; `baseline` is unchanged and recording ran against the same pinned product tree. git diff -U0 -- mobile/rpc-foundation/goldens | grep -E '^[+-]' \ | grep -vE '^(\+\+\+|---)' \ | grep -vE '^[+-] "(recorderSha256|adapterSha256|goldenFormatVersion)":' | wc -l 0 The seven `adapterSha256` values partition the 153 goldens by the module each was recorded through: 58 settings, 37 hosted review, 21 source control, 11 new-tab agents, 9 file inventory, 9 tasks, 8 workspace settings. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): stop pinning goldens to recorder inputs no recording can read The adapter split left three per-domain edits still moving all 153 headers: the mutant table, the per-family mutant registry beside it, and the probe-hole witness. None can change a recording -- the loader consults a mutant only when a mutant test asks for one, and no suite but the two recording drivers writes a golden -- so pinning them claimed a provenance the goldens do not have and charged every domain a full re-record for it. `mutants/` now holds the table, the registry, the reference states, the mutant suites and the probe-hole witness, and `recorderSha256` skips it. What makes that sound is that no recording can reach it: `operationModuleLoader` takes a resolved mutation spec instead of importing a table by name, so nothing on the recording path names `mutants/` at all. `mutants/mutant-seam.test.ts` checks exactly that, and fails if an engine file names the directory or anything outside imports from it. `recorderSha256` also pins only the suites in `recording-drivers.ts`, which `scripts/rpc-recording.mts` records from, so the two cannot drift. A suite that reads goldens, or writes one to a scratch directory, is no longer provenance for a recorded file. `OPERATION_EXPOSURES` went the other way, because it does change what a recording loads: withhold the resume-metadata exposure and exactly four goldens fail. Each domain module now declares its own exposures and gets its own loader, so `adapterSha256` pins the ones that reached each golden. Two assertions in the digest boundary test were vacuous: `join(root, '.')` normalises back to `root` and hit `recorderSha256`'s per-root cache, so the prose-is-ignored claim never recomputed anything. Each call now spells the root differently. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the RPC goldens under the mutant and driver exclusions Header-only, and no format bump: the header shape is unchanged. `recorderSha256` moves on all 153 because the engine set shrank, and `adapterSha256` moves on the 58 settings goldens because that module now carries its own exposure declaration. git diff -U0 HEAD~1 -- mobile/rpc-foundation/goldens | grep -E '^[+-]' \ | grep -vE '^(\+\+\+|---)' \ | grep -vE '^[+-] "(recorderSha256|adapterSha256)":' | wc -l 0 Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): restore the preferences actions the merge resolution dropped #20568 added `resume` and `trust` actions to the `settings.task-preferences` adapter while it still lived in `pilot-mount-adapters.ts`. This branch had already moved that adapter into `adapters/task-mount-adapters.ts`, so resolving the `pilot-mount-adapters.ts` conflict in favour of the registry merge silently discarded them and `tw-task-preferences-resume-write` failed to record at all ("Missing or completed request: ui.set#1"). Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the RPC goldens at main's tip after the merge All 208 goldens, header-only. `baseline` moves from50e752fc66to main's tipc6a7216984, `goldenFormatVersion` from 4 to 5, `recorderSha256` to the value of the engine with `adapters/` and `mutants/` carved out, and `adapterSha256` is new on every file. Nine distinct adapter digests over 208 goldens: each golden now pins only the module that mounts it. No observation moved. The whole-diff census against origin/main reports exactly four changed keys and nothing else: 208 "adapterSha256": 416 "baseline": 416 "goldenFormatVersion": 416 "recorderSha256": Recorded in place rather than through the README's detached-baseline dance: this branch changes no product file, so its tree at the merge is byte-identical toc6a7216984under mobile/src, src/shared and the lockfile, and the parity claim stays non-circular. README says so now. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): hold the recording drivers to the engine's mutant-seam rule The name scan exempted every `.test.ts` on the ground that a test cannot change a recording. Two of them can: the recording drivers are the recording path. A driver that read the mutant table by path rather than importing it passed both seam checks — the import scan sees no import, and the name scan waved it through as a test: const table = resolve(import.meta.dirname, 'mutants/operation-mutations.ts') console.log(readFileSync(table, 'utf8').length) at the top of `pilot-recordings.test.ts` gave 2 passed before, and after this change fails with ["pilot-recordings.test.ts"]. Only non-driver tests are exempt now. This file lives in `mutants/`, which `recorderSha256` skips, so no golden moves: the recorder suite is green on the existing 208 with zero dirty. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop the registry parameter no caller varies `pilotMountAdapters` took `registered` so a caller could mount a different module set; all six callers take the default. The header-digest tests vary the registry through `goldenRecording`, which keeps its own parameter and is where the stub roots need it. Engine source, so `recorderSha256` moves and the goldens follow in the next commit. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the RPC goldens after the registry parameter came out All 208, `recorderSha256` only. The re-record against the previous commit moves 416 lines, every one of them that field: 416 "recorderSha256": Against origin/main the picture is unchanged from the merge: 208 goldens, 0 added or deleted, 0 non-header lines, and exactly four keys differing — 208 "adapterSha256" 416 "baseline" 416 "goldenFormatVersion" 416 "recorderSha256" Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): wrap the recording README at the width the rest of it uses Seven lines this branch added ran past 100 columns, worst 124. No wording changed. Markdown is outside `recorderSha256`, so no golden moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): name the worktree overlay, not the archive that cannot work `git archive` was offered alongside a detached checkout as a way to lay this branch's recorder over the pinned baseline. It cannot work: the fence in scripts/rpc-recording.mts runs `git diff --quiet <baseline>` and an untracked-file check, both of which need a real `.git`. In an archive tree git exits non-zero for lack of a repository and the script reports "Product sources or lockfile differ from the pinned main baseline", which reads as a product mismatch that is not there. The transport agent lost time to exactly that. Names `git worktree add --detach` only, and says what the misleading failure looks like if someone tries an archive anyway. Markdown is outside `recorderSha256`, so no golden moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): close two ways an adapter module escapes its own digest Two holes, one class: the seam was checked by how an import was spelled and by what the register's values evaluated to, never by where they resolve or where they were written. Inward imports: the scan dropped every specifier starting with `..`, so `'../adapters/settings-mount-adapters'` climbed out of the directory and back into it unseen. A reviewer had `new-tab-agent-mount-adapters.ts` project a value read from the settings module, edited that module, and watched the mounted state change while the new-tab adapter digest held. Specifiers now resolve against the directory and anything landing back inside it fails: ["new-tab-agent-mount-adapters.ts imports ../adapters/settings-mount-adapters"] The register: `adapters/mounted-operation-modules.ts` is pinned by nothing — `recorderSha256` skips the directory and `adapterSha256` reads each entry's `source`. An `exposes` written inline there drives the mounted product module with no digest covering it. The same reviewer replaced the new-tab entry's `exposes` with a literal overriding `loadMobileNewTabAgentOptions`; twelve fence tests passed. Both `mounts` and `exposes` must now be identifiers the register imports from that entry's own module: ["new-tab-agent-mount-adapters.ts writes exposes inline instead of importing it"] Checked on the register's syntax, not its values, because an inline literal and an imported binding are indistinguishable once evaluated. Pinning the register in the engine digest would also close it, and is the wrong trade: every domain adding a register line would re-digest all 208 goldens, which is the conflict this PR exists to remove. Keeping the register an index costs nothing and keeps a domain's line local. Both fixes live in a `.test.ts` outside the drivers, so no golden moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): prove the mutant seam from the drivers out, not by spelling The seam rested on a grep for the literal `mutants`, which the exported `MUTANT_DIRECTORY` spells without containing. A reviewer had `pilot-mount-adapters.ts` read the mutant table through that constant and both checks passed. The README's claim — that nothing on the recording path names the directory — was false as written. Three changes, in order of strength: Reachability is now proved forward. The suite walks the static import graph from the two recording drivers and fails if any module under `mutants/` is in it. That answers the real question, what a golden's bytes can depend on, instead of the old inward scan's question, who mentions this directory. Non-emptiness is asserted on both sides so a graph that resolved nothing cannot pass by reaching nothing. The name scan covers both spellings, for paths a module can be read by rather than imported. The reviewer's probe now fails as ["pilot-mount-adapters.ts"]. `MUTANT_DIRECTORY` is no longer exported. Its two consumers were both tests of the digest, and they now spell the path instead, which is strictly better for them: a test that imports the constant follows a rename silently, while one that spells it fails on a rename — and that specific directory name is the whole soundness argument. This edits `recorder-digest.ts`, so the goldens re-record in the next commit. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): re-record the RPC goldens after MUTANT_DIRECTORY stopped being exported All 208, `recorderSha256` only. Against the previous commit the diff is 416 lines and every one of them is that field: 416 "recorderSha256": Against origin/main, unchanged: 208 goldens, 0 added or deleted, 0 non-header lines, four keys differing — 208 "adapterSha256" 416 "baseline" 416 "goldenFormatVersion" 416 "recorderSha256" Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the mutant seam's actual argument, and its edge The README claimed nothing on the recording path names `mutants/`. That was the old inward scan's claim and a reviewer falsified it with the exported constant. It now describes what the check does: a forward walk of the import graph from the two recording drivers, plus a name scan in both spellings for read-by-path, plus the constant no longer being exported. It also names the case neither closes — a path assembled from fragments at runtime. Markdown is outside `recorderSha256`, so no golden moves. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): prove the engine/adapter seam in both directions The inward scan only held adapters to the seam. An engine file importing an adapter executes code its own digest skips and that every golden recorded through another domain leaves out of `adapterSha256`, so the register is now the only crossing allowed from the engine side. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): name what the driver walk missed instead of counting it Seeding `seen` with the drivers made the driver-presence check true by construction, and the size bound compared a graph inflated by `typeof import` product modules against a recorder-sized number. Both go; the walk now reports the recording files it failed to reach, which is empty today and names an orphan engine file the moment one appears. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): reflow four paragraphs left ragged by the rewrap Orphan fragments only, no wording change: the golden-schema field list, the mutant-evidence paragraph, the probe-witness sentence and the re-anchor note. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): record the small-domain call sites before migrating them Thirteen new families cover the files, new-workspace, host-screen, home and worktree call sites step 4 migrates next: ownership capture, the preview loader and its terminal-artifact grant refresh, the artifact save, the tab doc's three shapes, the drawer's execution target and setup hook, the Codex reset-credit probe, the host view settings, the Home stats card and the three workspace catalog reads. Recorded against main's product code, so these are the parity baseline the refactor must not move. Four new adapter modules under adapters/ and no engine edit, so recorderSha256 is unmoved and every existing golden is byte-identical: 40 files added, none changed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): send the small-domain reads through RpcOperation Thirty-five of the domain's fifty-five raw-port references now go through a declared operation: the files domain's preview, artifact and tab-doc reads and its ownership capture, the New Workspace drawer, the host screen's metadata and view-settings mirror, the Home stats card, and all three workspace catalog reads. No behaviour change, and the oracle says so: zero goldens move. Acceptance is preserved call site by call site, including two that look like defects and stay that way — a refused worktree.listRetiredNames still settles as an empty registry rather than holding the previous names, and a refused ui.get on a null result still throws into the host screen's own catch. Where two call sites disagreed about one method, both policies are named: files.read and files.readPreview throw for a session file tab and skip for the preview screen, repo.hooks throws for task create and skips for the drawer, and status.get now carries a fourth family for the Codex capability probe's object-or-null rule. The drawer's SSH connect, SSH state and agent detection reuse the workspace-create operations the tasks migration already declared rather than restating them. Two things outside the call sites. requestSingleFlight now shares the params optionality rule that request already had, so an all-optional schema such as preflight.check can omit its params on both helpers instead of only one; that is type-level and puts nothing new on the wire. And the retired-names fixture resolved a reply with no `ok`, a shape no host sends, which read as a refusal once the acceptance policy routed on it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): drive the image arm of the preview loader The mutation census found two migrated operations that no recording reached: repointing files.readPreview or files.readTerminalArtifactPreview to a wrong method, and swapping either one's acceptance policy, changed nothing any golden observed. Both preview-load scenarios read a text path, so the loader's image branch was migrated with no wire behind it. Two scenarios now read an image path through each arm, and the adapter takes the path from the scenario instead of a constant. All four mutations die on the new goldens. They are recorded from the pinned baseline with this branch's recorder laid over it, so they are main's behaviour and not the migration's: the candidate run against the refactored tree compares clean. The adapter edit re-digests the nineteen goldens mounted through it. The diff is one adapterSha256 line each and no observation moves, which is what pinning the adapter per golden rather than per suite is for. Two casts also take the SAFETY form the house style asks for. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop the casts the changed-code gate flags Seven type assertions the gate counted as new, all removed rather than silenced where the type system could already answer. `normalizeMobileFilePreviewResponse` narrows on `ok` instead of asserting each arm; the snapshot adapter narrows on the fetch result's own discriminant; and the drawer's two probe reads go through one overloaded member read that keeps their optional-chaining behaviour. The remaining three keep a cast and now carry the rationale on the asserting line. No behaviour change. The two adapter edits re-digest the sixteen goldens mounted through them, one adapterSha256 line each with no observation moved, recorded from the pinned baseline the same way. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop the import the narrowing left behind RpcSuccess is no longer named once the response reads through its own discriminant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): record the host screen's worktree mutations Review of #20705 showed the `use-host-worktree-actions.ts` holdout reason was wrong: its only native call is the pinned-id write, and that sits behind `if (hostId)`, so mounting with no hostId never reaches it. Two scenarios in one new family, recorded from the pinned baseline with the call site still on the raw port. The first drives all three sends so the reply matrix covers each method; the second refuses `worktree.rm` to pin the row restore. The adapter is a new module, so no existing golden's `adapterSha256` moves and none of the 250 goldens already here is rewritten. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): send the host list's worktree mutations through RpcOperation Pin, remove and activate move onto operations in host-screen-operations.ts. All three skip on refusal, which is the policy each site already applied by hand: the pin and activate writes swallow everything in a `.catch`, and the remove restores the row on a refused reply. `worktree.set` therefore carries a second policy next to source-control's `worktree.set-review-link`, which throws; both are named. Zero goldens move. The inventory loses use-host-worktree-actions.ts and states the real reason the drawer's repo list stays: it renders the last-visited-repo hook, whose default import of async-storage the recorder's proxy refuses at module load, before the hostId guard the reviewer expected to save it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): type the worktree-action fixture row and follow the activation scan The fixture row I recorded from had four fields, which `tsc` rejects as a `Worktree`. Filling it out moves the five goldens of this branch's own new family and nothing else; the recorded sends are unchanged, only the projected row is. `mobile-worktree-activation-source.test.ts` scanned the hook for the literal `sendRequest('worktree.activate'`, which the previous commit replaced. It now asserts the operation call and its two flags in the hook, plus the method in host-screen-operations.ts, so the pair still pins the same wire. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): take the five deletions round-1 review asked for - `fileOwnershipRuntimeStatusRead` was `taskRuntimeStatusRead` field for field. It is now a re-export of it. The goldens are keyed on the scenario family, not the operation name, so `matrix-files.mutation-ownership-status.get-1.json` survives unchanged. - `readProbeMember`'s two overloads asserted shapes nothing checked. Gone; the nested read goes through the same single-signature function. - `normalizeMobileFilePreviewResponse` had no product caller. Deleted with its re-export; its twelve assertions now drive the accepted and refused arms directly. - The three inline copies of the accepted-result union are gone. They name each operation's own `interpret` return instead of importing `RpcAcceptedResult`: importing the contract would pull all three call sites into the cast fence, where their existing SAFETY assertions fail it. - `codex-reset-credit-capability-operation.ts` is now `-operations.ts`. No adapter names it, so no golden re-digests. Zero goldens move. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): give the skip verdict its own transport module The three settle helpers typed their interpret parameter as `ReturnType<typeof <op>.interpret>`, naming one operation while being called with others whose verdicts happen to be structurally identical. Narrowing a named reader would have silently retyped unrelated helpers. `RpcAcceptedResult` moves to `rpc-accepted-result.ts`, outside the cast fence's three region seeds, so a consumer can name the verdict without becoming an operation implementation. `rpc-operation-contract.ts` re-exports it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile): drop three sender aliases nothing imports MobileHostScreenRpcSender, MobileNewWorkspaceRpcSender and MobileWorktreeCatalogRpcSender each appeared only in the file that declared them. A named type with no consumer is a cost, not a boundary. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): say why a holdout is a holdout in the port inventory A site can be pointed at an operation without being mountable, so "cannot be migrated" was the wrong claim. The rule is record-first: the golden recorded against the old code is the only parity proof, so a site the recorder cannot mount cannot be recorded, and unrecorded sites do not migrate. Stated once in the list's header. codex-reset-credit.ts loads fine under the module loader; probed it, and its attempt-journal access throws on async-storage at call time before the send, with no guard to skip it. The old comment described it as a storage read around the send. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * docs(mobile): state the status.get policies without counting them "the fourth policy on this method", "the first of two" and "the second of two" were already wrong after round 1 folded the files family's status read into the tasks one. Each comment now states its own invariant, which no later policy can invalidate. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): stop the activation scan claiming to pin the wire `expect(operations).toContain("method: 'worktree.activate'")` passes whichever operation carries that method, so it survives swapping worktreePinWrite's and worktreeActivate's methods. tsc and the host-worktree-actions-pin-open-delete golden both fail on that swap; the scan keeps only what it can prove, which is that the callback sends through worktreeActivate with the two flags. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
Orca
中文 · 日本語 · 한국어 · Español · Français · Português
The AI Orchestrator for 100x builders.
Run Codex, ClaudeCode, OpenCode or Pi side-by-side — each in its own worktree, tracked in one place.
Download Orca
Features
Also in the box:
- Quick open — Search across worktrees, files, agents, commands, and repo context without leaving your flow.
- Account switcher & usage tracking — See Claude and Codex usage and rate-limit resets, and hot-swap accounts without re-logging in.
- Rich repo previews — Preview Markdown, images, PDFs, and repo docs in the workspace.
- Computer Use — Let agents operate desktop apps and visible UI when a workflow needs real interaction.
- Notifications and unread state — Know when an agent finishes or needs attention, then mark threads unread to come back later.
- And many, many more — we ship daily, so this list is perpetually behind. The changelog is the real feature list.
Supported Agents
Works with any CLI agent — if it runs in a terminal, it runs in Orca.
Claude Code
Codex
Grok
Cursor
GitHub Copilot
OpenCode
MiMo Code
Amp
OpenClaude
Antigravity
Pi
oh-my-pi
Hermes Agent
Devin
Goose
Auggie
Autohand Code
Charm
Cline
Codebuff
Command Code
Continue
Droid
Kilocode
Kimi
Kiro
Mistral Vibe
Qwen Code
Rovo Dev
+ any CLI agent
Install
Desktop — macOS, Windows, Linux
- Download from onOrca.dev
- Or grab a build directly: macOS Apple Silicon · macOS Intel · Windows (.exe) · Linux AppImage · All builds
- Running
orca serveon a headless Linux server? See the headless Linux server guide.
Or via a package manager:
# macOS (Homebrew)
brew install --cask stablyai/orca/orca
# Arch Linux (AUR) — or stably-orca-git to build from source
yay -S stably-orca-bin
Mobile Companion — iOS, Android
Pair with your desktop app to monitor and steer your agents from your phone.
- iOS: Download on the App Store or join TestFlight
- Android: Download APK 0.0.48 · Install guide
Community & Support
-
Discord: Join the community on Discord.
-
Twitter / X: Follow @orca_build for updates and announcements.
-
WeChat: Scan to join the Orca community WeChat group 8. Group 8 may be full; if so, scan the Group 9 QR code instead.
-
Feedback & Ideas: We ship fast. Missing something? Request a new feature.
-
Privacy: See the privacy & telemetry docs for what anonymous usage data Orca collects and how to opt out.
-
Show Support: Star this repo to follow along with our daily ships.
Developing
Want to contribute or run locally? See our CONTRIBUTING.md guide.
The relay that pairs the mobile app with a desktop host is also in this repository under
cloud/, with a separate pnpm workspace and setup guide.
Signed Builds
Windows code signing sponored/provided by SignPath.io, certificate by SignPath Foundation.
License
Orca is free and open source under the MIT License.












