Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/linux-source-smoke.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Linux source smoke

on:
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
linux-x64:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Check out source
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 24
cache: npm

- name: Install JavaScript dependencies
run: npm ci --ignore-scripts

- name: Run Linux dependency regressions
run: >-
node node_modules/vitest/vitest.mjs run
test/main/util/officecli-fetch.test.ts
test/main/util/ensure-dev-dependencies.test.ts
test/main/paths.test.ts

- name: Type-check
run: node node_modules/typescript/bin/tsc --noEmit

- name: Fetch pinned Linux OfficeCLI
run: node scripts/fetch-officecli.cjs --platform=linux-x64

- name: Verify native OfficeCLI contract
run: node bin/officecli-policy-gate.cjs --require-host-binary
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Installed and ready the first time you launch Orkas — each with its own skills

## Download

Packaged installers for macOS and Windows. Linux runs from source today — see [Quick start](#quick-start).
Packaged installers for macOS and Windows. glibc-based Linux x64/arm64 runs from source today — see [Quick start](#quick-start).

- **macOS Apple Silicon** → [Orkas-mac-arm64.dmg](https://orkas.ai/download/?source=gh-orkas&entry_point=github_readme&os=mac&arch=arm64&download=1)
- **macOS Intel** → [Orkas-mac-x64.dmg](https://orkas.ai/download/?source=gh-orkas&entry_point=github_readme&os=mac&arch=x64&download=1)
Expand Down Expand Up @@ -121,7 +121,7 @@ Yes. Beyond its own Commander and specialist agents, Orkas can drive external CL
Claude Desktop is a single assistant; CrewAI and LangChain are code-first frameworks. Orkas is a local-first multi-agent desktop app: the Commander coordinates specialist agents, keeps data and keys local, and gives each agent its own private skills and memory. See the [full comparisons](https://orkas.ai/compare/orkas-vs-langchain?source=gh-orkas).

**Which platforms does Orkas support?**
macOS (Apple Silicon and Intel) and Windows 10+ have packaged installers. Linux runs from source today — same app, no installer yet. The source build needs Node 20+ and Python 3.
macOS (Apple Silicon and Intel) and Windows 10+ have packaged installers. glibc-based Linux x64/arm64 runs from source today, with no installer yet. Local speech transcription is not currently available on Linux. Alpine and other musl-based distributions are not supported. The source build needs Node 20+ and Python 3.

**Is Orkas free and open source?**
Yes — the app is MIT licensed and free to use. Bring your own model keys and you pay only your model providers; Orkas never takes a cut. Optionally, the desktop app also offers a built-in **Orkas model** for people who don't want to manage API keys — that one is billed by Orkas in credits (membership or credit packs). It is entirely opt-in, and every other feature works on your own keys. [Pricing →](https://orkas.ai/pricing/?source=gh-orkas)
Expand All @@ -132,7 +132,7 @@ Yes — the app is MIT licensed and free to use. Bring your own model keys and y

Want a packaged installer instead? See [Download](#download) above. To run from source — currently the way to run Orkas on Linux:

**Requirements**: Node 20+ · Python 3 · macOS / Windows 10+ / recent Linux
**Requirements**: Node 20+ · Python 3 · macOS / Windows 10+ / glibc-based Linux x64 or arm64

```bash
git clone https://github.com/Orkas-AI/Orkas.git
Expand All @@ -141,6 +141,8 @@ cd Orkas
run.cmd # Windows
```

Linux source runs currently require glibc. Alpine and other musl-based distributions are not supported, and local speech transcription is unavailable on Linux.

`run.sh` / `run.cmd` auto-installs dependencies and downloads the embedding model (~95 MB). First launch creates a workspace under `~/.orkas/` (macOS / Linux) or `<smallest non-system drive>:\.orkas\` (Windows). Then open **Settings → AI Providers** to add an API key or OAuth.

---
Expand Down
8 changes: 5 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Orkas 是一个开源、本地优先的多智能体桌面应用。你描述目

## 下载

macOS 和 Windows 提供安装包。Linux 目前需从源码运行 —— 见 [快速开始](#快速开始)。
macOS 和 Windows 提供安装包。基于 glibc 的 Linux x64/arm64 目前需从源码运行 —— 见 [快速开始](#快速开始)。

- **macOS Apple 芯片** → [Orkas-mac-arm64.dmg](https://orkas.ai/download/?source=gh-orkas&entry_point=github_readme&os=mac&arch=arm64&download=1)
- **macOS Intel** → [Orkas-mac-x64.dmg](https://orkas.ai/download/?source=gh-orkas&entry_point=github_readme&os=mac&arch=x64&download=1)
Expand Down Expand Up @@ -121,7 +121,7 @@ Orkas 是一个开源、本地优先的多智能体桌面应用。一个超强
Claude Desktop 是单个助理;CrewAI 和 LangChain 是代码优先的框架。Orkas 是一个本地优先的多智能体桌面应用:指挥官协调多个专业智能体,数据与 key 留在本地,每个智能体拥有私有技能与记忆。见[逐项对比](https://orkas.ai/compare/orkas-vs-langchain?source=gh-orkas)。

**支持哪些平台?**
macOS(Apple 芯片与 Intel)和 Windows 10+ 提供安装包。Linux 目前从源码运行 —— 功能相同,只是还没有安装包。源码运行需要 Node 20+ 与 Python 3。
macOS(Apple 芯片与 Intel)和 Windows 10+ 提供安装包。基于 glibc 的 Linux x64/arm64 目前从源码运行,暂时没有安装包,且本地语音转写功能不可用。当前不支持 Alpine 及其他基于 musl 的发行版。源码运行需要 Node 20+ 与 Python 3。

**Orkas 免费且开源吗?**
是的 —— 应用本身 MIT 许可证、免费使用。自带模型 key 时,你只需为你的模型服务商付费,Orkas 不抽成。此外,桌面版还提供一个可选的内置 **Orkas 模型**,供不想自己配置 key 的用户使用 —— 这部分由 Orkas 按 credits 计费(会员或 credits 包)。它完全可选,其余所有功能用你自己的 key 即可。[价格 →](https://orkas.ai/pricing/?source=gh-orkas)
Expand All @@ -132,7 +132,7 @@ macOS(Apple 芯片与 Intel)和 Windows 10+ 提供安装包。Linux 目前

想直接用安装包?见上方 [下载](#下载)。以下是从源码运行的方式 —— 也是目前在 Linux 上运行 Orkas 的方式:

**环境要求**:Node 20+ · Python 3 · macOS / Windows 10+ / 较新的 Linux
**环境要求**:Node 20+ · Python 3 · macOS / Windows 10+ / 基于 glibc 的 Linux x64 或 arm64

```bash
git clone https://github.com/Orkas-AI/Orkas.git
Expand All @@ -141,6 +141,8 @@ cd Orkas
run.cmd # Windows
```

Linux 源码运行目前需要 glibc,不支持 Alpine 及其他基于 musl 的发行版;本地语音转写功能在 Linux 上也暂不可用。

`run.sh` / `run.cmd` 会自动安装依赖并下载嵌入模型(约 95 MB)。首次启动会在 `~/.orkas/`(macOS / Linux)或 `<最小的非系统盘>:\.orkas\`(Windows)下创建工作区。随后进入 **设置 → AI 服务商** 配置 API key 或 OAuth。

---
Expand Down
20 changes: 14 additions & 6 deletions scripts/ensure-dev-dependencies.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ function run(label, script, args = []) {
if (result.status !== 0) throw new Error(`${label} failed with status ${result.status}`);
}

function shouldProvisionWhisper(platform = process.platform, arch = process.arch) {
return Boolean(WHISPER_RUNTIME_CONTRACT.targets[`${platform}-${arch}`]);
}

function main() {
console.log(`[dev-deps] preparing built-in dependencies for ${process.platform}-${process.arch}`);
run('notification permission addon', 'scripts/build-notification-permission-addon.cjs', [
Expand All @@ -43,7 +47,7 @@ function main() {
// without a contract target the fetch script must keep failing (packaging
// gates rely on that strictness); here it is simply not required — speech
// transcription degrades to an actionable error at runtime instead.
if (WHISPER_RUNTIME_CONTRACT.targets[`${process.platform}-${process.arch}`]) {
if (shouldProvisionWhisper(process.platform, process.arch)) {
run('Whisper', 'scripts/fetch-whisper.cjs', [
'--platform', process.platform,
'--arch', process.arch,
Expand All @@ -54,9 +58,13 @@ function main() {
console.log('[dev-deps] built-in dependencies ready');
}

try {
main();
} catch (err) {
console.error(`[dev-deps] failed: ${err.message}`);
process.exit(1);
module.exports = Object.freeze({ shouldProvisionWhisper });

if (require.main === module) {
try {
main();
} catch (err) {
console.error(`[dev-deps] failed: ${err.message}`);
process.exit(1);
}
}
12 changes: 8 additions & 4 deletions src/main/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,15 +708,19 @@ const OFFICECLI_ASSETS: Readonly<Record<string, string>> = {
'win32-arm64': 'officecli-win-arm64.exe',
};

/** Absolute path to the OfficeCLI binary for the current platform/arch, or
* null when no asset ships for it. Shipped via electron-builder
/** Absolute path to the OfficeCLI binary for the requested platform/arch
* (the current host by default), or null when no asset ships for it. Shipped
* via electron-builder
* `extraResources`:
* dev: PC/resources/officecli/
* packed: <app>/Contents/Resources/officecli/ (darwin)
* <app>/resources/officecli/ (win/linux)
*/
export function officeCliBinaryPath(): string | null {
const asset = OFFICECLI_ASSETS[`${process.platform}-${process.arch}`];
export function officeCliBinaryPath(
platform: string = process.platform,
arch: string = process.arch,
): string | null {
const asset = OFFICECLI_ASSETS[`${platform}-${arch}`];
if (!asset) return null;
const rp = (process as unknown as { resourcesPath?: string }).resourcesPath;
const dir = (rp && !rp.includes(`${path.sep}node_modules${path.sep}electron${path.sep}`))
Expand Down
16 changes: 16 additions & 0 deletions test/main/paths.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ describe('paths › top-level (users.json / device.json / logs / venv)', () => {
});
});

describe('paths › OfficeCLI runtime', () => {
it.each([
['linux', 'x64', 'officecli-linux-x64'],
['linux', 'arm64', 'officecli-linux-arm64'],
])('resolves the bundled %s-%s asset', async (platform, arch, asset) => {
const p = await import('../../src/main/paths');
expect(p.officeCliBinaryPath(platform, arch))
.toBe(path.join(p.PC_ROOT, 'resources', 'officecli', asset));
});

it('returns null for an unsupported platform target', async () => {
const p = await import('../../src/main/paths');
expect(p.officeCliBinaryPath('freebsd', 'x64')).toBeNull();
});
});

describe('paths › cloud-synced per-user', () => {
it('chats / attachments / sessions / contexts / memory land under <uid>/cloud/', async () => {
const p = await import('../../src/main/paths');
Expand Down
26 changes: 26 additions & 0 deletions test/main/util/ensure-dev-dependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import * as path from 'node:path';

const { shouldProvisionWhisper } = require(
path.join(process.cwd(), 'scripts', 'ensure-dev-dependencies.cjs'),
) as {
shouldProvisionWhisper: (platform: string, arch: string) => boolean;
};

describe('ensure-dev-dependencies.cjs', () => {
it.each([
['darwin', 'arm64'],
['darwin', 'x64'],
['win32', 'x64'],
])('keeps Whisper required for supported target %s-%s', (platform, arch) => {
expect(shouldProvisionWhisper(platform, arch)).toBe(true);
});

it.each([
['linux', 'x64'],
['linux', 'arm64'],
['win32', 'arm64'],
])('does not provision Whisper for unsupported target %s-%s', (platform, arch) => {
expect(shouldProvisionWhisper(platform, arch)).toBe(false);
});
});
16 changes: 13 additions & 3 deletions test/main/util/officecli-fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import * as os from 'node:os';
import * as path from 'node:path';

const TEST_NODE = process.env.ORKAS_TEST_NODE || process.execPath;
const OFFICECLI_SPEC = require(path.join(process.cwd(), 'scripts', 'fetch-officecli.cjs')) as {
ASSETS: Readonly<Record<string, string>>;
SHA256: Readonly<Record<string, string>>;
};

let tmpDir: string;

Expand Down Expand Up @@ -50,19 +54,25 @@ describe('fetch-officecli.cjs', () => {
expect(`${r.stdout}\n${r.stderr}`).toContain('officecli-mac-arm64 missing');
});

it('resolves the linux platform keys to pinned upstream assets', () => {
it.each([
['linux-x64', 'officecli-linux-x64', 'da07d4f787d7c85724104294ac023c89971ddfaee93ebb183b289282b8f869cc'],
['linux-arm64', 'officecli-linux-arm64', '39008c7f76d202858637810553ef14e2cd3e7f61485fdcf2011f26967a7babd1'],
])('resolves %s to its pinned upstream asset', (platform, asset, sha256) => {
expect(OFFICECLI_SPEC.ASSETS[platform]).toBe(asset);
expect(OFFICECLI_SPEC.SHA256[asset]).toBe(sha256);

const r = spawnSync(TEST_NODE, [
path.join(process.cwd(), 'scripts', 'fetch-officecli.cjs'),
`--root=${tmpDir}`,
'--platform=linux-x64',
`--platform=${platform}`,
'--check',
], {
cwd: process.cwd(),
encoding: 'utf8',
});

expect(r.status).toBe(1);
expect(`${r.stdout}\n${r.stderr}`).toContain('officecli-linux-x64 missing');
expect(`${r.stdout}\n${r.stderr}`).toContain(`${asset} missing`);
});

it('resumes an existing partial download with a Range request', async () => {
Expand Down