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
4 changes: 4 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,13 @@ Hermes、OpenCode、pi**。そのうち 8 つはプロバイダーにバイン
**macOS(Apple Silicon)は Homebrew でのインストールをおすすめします。**

```sh
brew tap rushteam/tap
brew trust --cask rushteam/tap/aiopt
brew install --cask rushteam/tap/aiopt
```

新しい Homebrew は信頼していないサードパーティ tap の cask を読み込まないため、先に `brew trust` を実行します。

この cask は **AiOpt.app** を `/Applications` に置き、macOS の隔離フラグを消すので、初回起動時に Gatekeeper の
確認は出ません。`brew upgrade --cask aiopt` で新しいリリースに更新し、`brew uninstall --cask aiopt` でアプリを
削除します。`--zap` を付けると、保存済みのキーを含むデータも削除されます。
Expand Down
4 changes: 4 additions & 0 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,13 @@ Hermes, OpenCode, pi**. 그중 8개는 공급자에 바인딩할 수 있습니
**macOS(Apple Silicon)는 Homebrew로 설치하는 것을 권장합니다.**

```sh
brew tap rushteam/tap
brew trust --cask rushteam/tap/aiopt
brew install --cask rushteam/tap/aiopt
```

최신 Homebrew는 신뢰하지 않은 서드파티 tap의 cask를 불러오지 않으므로 먼저 `brew trust`를 실행합니다.

이 cask는 **AiOpt.app**을 `/Applications`에 설치하고 macOS 격리 플래그를 지우므로, 첫 실행 때 Gatekeeper
확인 창이 뜨지 않습니다. `brew upgrade --cask aiopt`로 새 릴리스로 업그레이드하고, `brew uninstall --cask aiopt`로
앱을 제거합니다. `--zap`을 붙이면 저장된 키를 포함한 데이터도 삭제됩니다.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,14 @@ adopts `~/.agents/skills` when that directory already exists.
**macOS (Apple Silicon): install with Homebrew.** This is the recommended route:

```sh
brew tap rushteam/tap
brew trust --cask rushteam/tap/aiopt
brew install --cask rushteam/tap/aiopt
```

Recent Homebrew refuses casks from a third-party tap until you trust them, hence the
`brew trust` step.

The cask puts **AiOpt.app** in `/Applications` and clears the macOS quarantine flag, so the
first launch shows no Gatekeeper prompt. `brew upgrade --cask aiopt` moves to a new release;
`brew uninstall --cask aiopt` removes the app, and adding `--zap` also deletes its data,
Expand Down
4 changes: 4 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,13 @@ AiOpt 用一个统一的供应商管理入口、每个 Agent 一个切换开关
**macOS(Apple Silicon)推荐用 Homebrew 安装:**

```sh
brew tap rushteam/tap
brew trust --cask rushteam/tap/aiopt
brew install --cask rushteam/tap/aiopt
```

新版 Homebrew 不会加载未信任的第三方 tap 里的 cask,所以需要先执行 `brew trust`。

该 cask 会把 **AiOpt.app** 装进 `/Applications` 并清除 macOS 的隔离标记,首次启动不会弹出 Gatekeeper 提示。
`brew upgrade --cask aiopt` 升级到新版本;`brew uninstall --cask aiopt` 卸载应用,加上 `--zap` 还会删除它的
数据,包括已保存的密钥。
Expand Down
42 changes: 34 additions & 8 deletions apps/desktop/src/main/workbench/__tests__/taskChanges.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { execFile, execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import {
GIT_SAFE_CONFIG,
isCommitId,
Expand All @@ -11,7 +11,7 @@ import {
readHeadCommit,
readTaskChanges,
splitPatch,
type GitExec,
type GitExecResult,
} from '../taskChanges';

describe('task changes parsing', () => {
Expand Down Expand Up @@ -56,27 +56,53 @@ function gitAvailable(): boolean {
}
}

const realExec: GitExec = (args) =>
/**
* The few host variables git needs to start. Windows git fails without SystemRoot /
* USERPROFILE / TEMP; everything else (the user's git config included) stays out.
*/
function hostEnv(): Record<string, string> {
const env: Record<string, string> = {};
for (const name of ['PATH', 'HOME', 'SystemRoot', 'windir', 'USERPROFILE', 'TEMP', 'TMP', 'PATHEXT', 'ComSpec']) {
const value = process.env[name];
if (value) env[name] = value;
}
return env;
}

// An empty file, not os.devNull: Windows git rejects \\.\nul as a config path.
let emptyGlobalConfig = '';
let globalConfigDir = '';
beforeAll(() => {
globalConfigDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aiopt-taskchanges-cfg-'));
emptyGlobalConfig = path.join(globalConfigDir, 'gitconfig');
fs.writeFileSync(emptyGlobalConfig, '');
});
afterAll(() => {
fs.rmSync(globalConfigDir, { recursive: true, force: true });
});

/** A GitExec that also keeps stderr, so a failed setup step says why. */
const realExec: (args: readonly string[]) => Promise<GitExecResult & { stderr: string }> = (args) =>
new Promise((resolve, reject) => {
execFile(
'git',
[...args],
{
encoding: 'utf8',
env: {
PATH: process.env.PATH ?? '',
...hostEnv(),
GIT_CONFIG_NOSYSTEM: '1',
GIT_CONFIG_GLOBAL: os.devNull,
GIT_CONFIG_GLOBAL: emptyGlobalConfig,
GIT_AUTHOR_NAME: 't',
GIT_AUTHOR_EMAIL: 't@example.com',
GIT_COMMITTER_NAME: 't',
GIT_COMMITTER_EMAIL: 't@example.com',
},
},
(err, stdout) => {
(err, stdout, stderr) => {
const code = err && typeof (err as { code?: unknown }).code === 'number' ? (err as { code: number }).code : 0;
if (err && typeof (err as { code?: unknown }).code !== 'number') reject(err);
else resolve({ code, stdout });
else resolve({ code, stdout, stderr });
},
);
});
Expand All @@ -85,7 +111,7 @@ describe.skipIf(!gitAvailable())('readTaskChanges (real git)', () => {
let dir = '';
const run = async (...args: string[]) => {
const out = await realExec(['-C', dir, ...args]);
if (out.code !== 0) throw new Error(`git ${args.join(' ')} failed`);
if (out.code !== 0) throw new Error(`git ${args.join(' ')} failed: ${out.stderr ?? ''}`);
};

beforeEach(async () => {
Expand Down
4 changes: 3 additions & 1 deletion docs/dev-rules/development-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ To release:
on `main` here and runs `brew style`, `brew audit --cask --strict --online`, and a real
install. A change of only `version` and `sha256` is committed to the tap's `main` directly.
Any other difference opens a pull request in the tap for review. The cask is the primary
macOS install route: `brew install --cask rushteam/tap/aiopt`.
macOS install route: `brew tap rushteam/tap`, `brew trust --cask rushteam/tap/aiopt`, then
`brew install --cask rushteam/tap/aiopt` (recent Homebrew refuses an untrusted tap's casks,
so the sync workflow trusts the cask before its audit too).

So change the cask's structure (url, postflight, zap, caveats) here, through a PR. The
`version` and `sha256` in this repository's copy may lag the tap and need no bump. Drafts
Expand Down
Loading