Skill: wimi321/computer-use-macos (version 0.2.2)

--- name: computer-use-macos version: 0.2.2 description: Top-level macOS computer-use skill with a bundled standalone runtime that bootstraps itself without any local Claude installation, private native modules, or extracted app assets. tags: - skill - macos - computer-use - automation - mcp --- # macOS Computer-Use Skill Use this skill when the task needs a portable macOS computer-use skill bundled with its own standalone runtime and MCP server. ## What this skill does - uses the bundled `macos-computer-use-skill` project under the installed skill directory - builds the standalone MCP server - lets the server auto-bootstrap its Python runtime on first launch - avoids any dependency on local Claude binaries, `.node` modules, or extracted app assets - stays explicitly macOS-only because the underlying desktop-control backend is macOS-specific ## Default bundled project path After installation, assume the standalone project lives at: ```bash ~/.codex/skills/computer-use-macos/project ``` If the user installed the skill under a custom `CODEX_HOME`, use that equivalent path instead. ## Build Always build from the bundled project: ```bash cd ~/.codex/skills/computer-use-macos/project npm install npm run build ``` ## Run ```bash cd ~/.codex/skills/computer-use-macos/project node dist/cli.js ``` The first real run will automatically create `.runtime/venv` and install the public Python dependencies. ## Validation notes - On real macOS hosts, prefer validating typing through the MCP `type` tool path, not only low-level helper calls. - Version `0.2.2` routes ordinary macOS text input through clipboard paste when available, avoiding IME/input-source corruption seen with per-key typing under Chinese input methods. ## Guardrails - Treat this host as trusted-local only. - Do not tell the user to search their local Claude install for binaries or hidden assets. - Be explicit that this runtime is standalone and uses public dependencies only. - Mention that the current runtime reports `screenshotFiltering: none`, so action gating is handled at the MCP layer.

path: agents/openai.yaml
content: interface:
  display_name: "macOS Computer-Use Skill"
  short_description: "Use the bundled top-level macOS computer-use skill and standalone runtime"
  default_prompt: "Use the bundled top-level macOS computer-use skill, build the standalone runtime if needed, let it self-bootstrap, and avoid any dependency on a local Claude installation or hidden app assets."


path: project/README.ja.md
content: <div align="center">
  <img src="./assets/hero.svg" alt="macos-computer-use-skill hero" width="100%" />
  <h1>macOS Computer-Use Skill</h1>
  <p><strong>macOS 向けのトップレベル portable skill。standalone runtime と MCP server を同梱しています。</strong></p>
  <p>
    <a href="https://github.com/wimi321/macos-computer-use-skill">GitHub</a>
    ·
    <a href="https://clawhub.ai/wimi321/computer-use-macos">ClawHub</a>
    ·
    <a href="./README.md">English</a>
    ·
    <a href="./README.zh-CN.md">简体中文</a>
  </p>
</div>

## ClawHub からインストール

この skill は ClawHub に [`computer-use-macos`](https://clawhub.ai/wimi321/computer-use-macos) として公開済みです。

```bash
clawhub install computer-use-macos
```

ソース一式も欲しい場合は、このまま GitHub セットアップを参照してください。

## このプロジェクトの位置づけ

このリポジトリは同時に:

- トップレベルの `skill`
- 独立した macOS runtime
- agent エコシステム向けの computer-use MCP server

として設計されています。Codex 専用ではなく、skill という配布形態そのものをポータブルにしています。

## このプロジェクトの目的

目標は明確です。

- ローカル Claude に依存しない
- private な `.node` バイナリに依存しない
- どこかのインストール済み Claude から内部資産を拾わない
- skill を入れたら、そのまま computer use が動く

このリポジトリはその前提で再構成されています。

## できること

- トップレベル macOS computer-use skill
- スクリーンショット、マウス、キーボード、アプリ起動、ウィンドウ/ディスプレイ情報、クリップボードを扱う standalone MCP server
- 公開依存のみ: `Node.js + Python + pyautogui + mss + Pillow + pyobjc`
- 初回起動時に `.runtime/venv` を自動作成し、Python 依存を自動インストール
- `~/.codex/skills/computer-use-macos/project` に本体まで配置される skill install
- 抽出した TypeScript computer-use ツール層を維持しつつ、実行バックエンドを完全独立化

## ローカル検証済み

実機の macOS で以下を確認しました。

- runtime bootstrap
- 権限チェック
- ディスプレイ列挙
- スクリーンショット取得
- 最前面アプリ検出
- ポインタ下アプリ検出
- ウィンドウとディスプレイの対応付け
- クリップボード読み書き
- MCP `type` ツール経由の GUI 入力 smoke test
- MCP server 起動

## 0.2.2 で修正したこと

実機テスト中に重要な不具合を確認しました。中国語 IME / 入力ソースが有効な状態では、通常の ASCII テキストでもキーを 1 文字ずつ送る方式だと内容が壊れることがありました。

`0.2.2` では、macOS 上でクリップボード書き込み権限がある場合、通常の複数字入力も優先的にクリップボード貼り付け経路へルーティングします。これにより、英字入力が IME 状態に引きずられて壊れるケースを避けられます。

## アーキテクチャ

```mermaid
flowchart LR
    A[Codex / MCP Client] --> B[macos-computer-use-skill]
    B --> C[抽出した TypeScript MCP ツール層]
    B --> D[Standalone Python bridge]
    D --> E[pyautogui]
    D --> F[mss + Pillow]
    D --> G[pyobjc Cocoa + Quartz]
    E --> H[マウス / キーボード]
    F --> I[スクリーンショット]
    G --> J[アプリ / ディスプレイ / クリップボード / ウィンドウ]
```

## インストール

### 1. クローンして Node 依存を入れる

```bash
git clone https://github.com/wimi321/macos-computer-use-skill.git
cd macos-computer-use-skill
npm install
npm run build
```

### 2. サーバーを起動

```bash
node dist/cli.js
```

初回起動時に自動で以下を実行します。

- `.runtime/venv` の作成
- 必要なら `pip` の bootstrap
- `runtime/requirements.txt` に基づく Python 依存の導入

Claude Desktop も private native module も不要です。

## MCP 設定

```json
{
  "mcpServers": {
    "computer-use": {
      "command": "node",
      "args": [
        "/absolute/path/to/macos-computer-use-skill/dist/cli.js"
      ],
      "env": {
        "CLAUDE_COMPUTER_USE_DEBUG": "0",
        "CLAUDE_COMPUTER_USE_COORDINATE_MODE": "pixels"
      }
    }
  }
}
```

参考: [`examples/mcp-config.json`](./examples/mcp-config.json)

## Skill インストール

同梱 skill: [`skill/computer-use-macos`](./skill/computer-use-macos)

ClawHub から直接入れる方法と、このリポジトリから入れる方法の両方があります。

### Option A: ClawHub からインストール

```bash
clawhub install computer-use-macos
```

### Option B: リポジトリからインストール

```bash
bash skill/computer-use-macos/scripts/install.sh
```

インストーラは以下をまとめてコピーします。

- skill メタデータ
- standalone プロジェクト本体
- runtime bootstrap ファイル

インストール後の既定パス:

```bash
~/.codex/skills/computer-use-macos/project
```

つまり元の clone が消えても、インストール済み skill 側のプロジェクトで動かせます。

## 実行メモ

### 権限

macOS では引き続き以下が必要です。

- Accessibility
- Screen Recording

この standalone host は MCP フローの中で両方を確認します。

### スクリーンショット filtering

この runtime は `screenshotFiltering: none` を返します。

つまり:

- スクリーンショット自体は compositor レベルでは絞り込まれない
- その代わり allowlist / tier / action gate は MCP 側で継続して適用される

### 対応プラットフォーム

この実装は現在 `macOS only` です。Windows や Linux 向け backend はまだ含まれていません。

## 検証マトリクス

今回この Mac で完了した実テスト:

- `npm run check`
- `npm run build`
- `runtime/mac_helper.py` の Python compile check
- Accessibility / Screen Recording の権限確認
- アクティブディスプレイの列挙
- 実デスクトップのスクリーンショット取得
- 実行中 / インストール済みアプリの列挙
- 最前面アプリの検出
- 新しい `CODEX_HOME` への skill インストール
- インストール済み `project/` での `npm install && npm run build`
- TextEdit を使った MCP `type` ツールの GUI 入力ラウンドトリップ検証

## 主要コマンド

```bash
npm run build
node dist/cli.js
```

```bash
node --input-type=module -e "import { callPythonHelper } from './dist/computer-use/pythonBridge.js'; console.log(await callPythonHelper('list_displays', {}));"
```

## リポジトリ構成

```text
src/
  computer-use/
    executor.ts
    hostAdapter.ts
    pythonBridge.ts
  vendor/computer-use-mcp/
runtime/
  mac_helper.py
  requirements.txt
skill/
  computer-use-macos/
examples/
assets/
```

## 任意の環境変数

- `CLAUDE_COMPUTER_USE_DEBUG=1`
- `CLAUDE_COMPUTER_USE_COORDINATE_MODE=pixels`
- `CLAUDE_COMPUTER_USE_CLIPBOARD_PASTE=1`
- `CLAUDE_COMPUTER_USE_MOUSE_ANIMATION=1`
- `CLAUDE_COMPUTER_USE_HIDE_BEFORE_ACTION=0`

## Roadmap

- app icon 抽出の改善
- nested helper app のフィルタ精度向上
- MCP 統合テストの拡充
- 配布しやすいリリース成果物の追加

## License

MIT

## Credits

Claude Code computer-use ワークフローから再利用可能な TypeScript ロジックを抽出し、そこに完全独立の公開 macOS runtime を接続したプロジェクトです。


path: project/README.md
content: <div align="center">
  <img src="./assets/hero.svg" alt="macos-computer-use-skill hero" width="100%" />
  <h1>macOS Computer-Use Skill</h1>
  <p><strong>A top-level portable skill for macOS with a bundled standalone runtime and MCP server.</strong></p>
  <p>
    <a href="https://github.com/wimi321/macos-computer-use-skill">GitHub</a>
    ·
    <a href="https://clawhub.ai/wimi321/computer-use-macos">ClawHub</a>
    ·
    <a href="./README.zh-CN.md">简体中文</a>
    ·
    <a href="./README.ja.md">日本語</a>
  </p>
</div>

## Install From ClawHub

Published on ClawHub as [`computer-use-macos`](https://clawhub.ai/wimi321/computer-use-macos).

```bash
clawhub install computer-use-macos
```

If you want the source repo as well, keep reading for the full GitHub setup.

## Positioning

This repository is best understood as:

- a top-level `skill`
- a bundled standalone macOS runtime
- a computer-use MCP server for agent ecosystems

It is not just for Codex. The skill packaging is intentionally portable, so the same project can be adapted for ecosystems that consume skill-style distributions.

## Why This Project Exists

The original Claude Code computer-use stack was excellent, but the user requirement here was stricter:

- no piggybacking on a local Claude install
- no private `.node` binaries
- no "works if you already extracted internal assets"
- install the skill, launch the server, and use it

This repository now delivers exactly that on macOS.

## What You Get

- top-level macOS computer-use skill
- standalone MCP server for screenshots, mouse, keyboard, app launch, display switching context, and clipboard
- public dependency chain only: `Node.js + Python + pyautogui + mss + Pillow + pyobjc`
- first-run runtime bootstrap: the server creates its own virtualenv and installs dependencies automatically
- bundled skill install that copies the full project into `~/.codex/skills/computer-use-macos/project`
- extracted TypeScript tool layer from the original computer-use workflow, re-wired to a fully independent backend

## Current Status

This repository has been validated locally on a real macOS machine with:

- runtime bootstrap
- permission checks
- display enumeration
- screenshot capture
- frontmost app detection
- app-under-point lookup
- window-to-display resolution
- clipboard read/write
- MCP `type` tool GUI typing smoke tests
- MCP server startup

## What Was Fixed In 0.2.2

During real-device testing, we hit a macOS-specific bug: under a Chinese IME/input source, ordinary ASCII text could be corrupted when the tool typed one key at a time.

Version `0.2.2` fixes that by preferring clipboard-routed typing on macOS for normal multi-character text when clipboard write is available. That keeps the standalone skill usable even when the current input source is not plain U.S. keyboard mode.

## Architecture

```mermaid
flowchart LR
    A[Codex / MCP Client] --> B[macos-computer-use-skill]
    B --> C[Extracted TypeScript MCP tools]
    B --> D[Standalone Python bridge]
    D --> E[pyautogui]
    D --> F[mss + Pillow]
    D --> G[pyobjc Cocoa + Quartz]
    E --> H[Mouse / Keyboard]
    F --> I[Screenshots]
    G --> J[Apps / Displays / Clipboard / Windows]
```

## Install

### 1. Clone and install Node deps

```bash
git clone https://github.com/wimi321/macos-computer-use-skill.git
cd macos-computer-use-skill
npm install
npm run build
```

### 2. Start the server

```bash
node dist/cli.js
```

On first launch, the project will automatically:

- create `.runtime/venv`
- bootstrap `pip` if needed
- install the Python runtime dependencies from `runtime/requirements.txt`

No Claude desktop app. No private native modules. No local extraction path required.

## MCP Configuration

Example config:

```json
{
  "mcpServers": {
    "computer-use": {
      "command": "node",
      "args": [
        "/absolute/path/to/macos-computer-use-skill/dist/cli.js"
      ],
      "env": {
        "CLAUDE_COMPUTER_USE_DEBUG": "0",
        "CLAUDE_COMPUTER_USE_COORDINATE_MODE": "pixels"
      }
    }
  }
}
```

See [`examples/mcp-config.json`](./examples/mcp-config.json).

## Skill Install

This repo ships a top-level skill at [`skill/computer-use-macos`](./skill/computer-use-macos).

You can install it either from ClawHub or from this repository.

### Option A: Install from ClawHub

```bash
clawhub install computer-use-macos
```

### Option B: Install from the repo

Install it with:

```bash
bash skill/computer-use-macos/scripts/install.sh
```

The installer copies:

- the skill metadata
- the bundled standalone project
- the runtime bootstrap files

After installation, the default project path becomes:

```bash
~/.codex/skills/computer-use-macos/project
```

That means the installed skill can work even if the original clone disappears.

## Runtime Notes

### Permissions

macOS still requires:

- Accessibility
- Screen Recording

The standalone host checks both and reports them through the MCP flow.

### Screenshot Filtering

This standalone runtime reports `screenshotFiltering: none`.

That means:

- screenshots are not compositor-filtered
- the original allowlist / permission / tier logic still protects actions at the MCP layer

### Platform Scope

This project is intentionally focused on `macOS` desktop computer use. The current runtime is not a Windows or Linux backend.

Covered capabilities:

- screenshots
- mouse control
- keyboard input
- frontmost app inspection
- installed/running app discovery
- window-to-display mapping
- clipboard access
- app launch

## Validation Matrix

Real tests completed on this Mac:

- `npm run check`
- `npm run build`
- Python helper compile check for `runtime/mac_helper.py`
- permission probe: Accessibility + Screen Recording both granted
- display discovery on the active display
- real screenshot capture from the desktop
- running / installed app enumeration
- frontmost-app detection
- bundled skill install into a clean `CODEX_HOME`
- bundled project `npm install && npm run build`
- real GUI typing round-trip through the MCP `type` tool into TextEdit with exact clipboard verification

## Example Commands

```bash
npm run build
node dist/cli.js
```

```bash
node --input-type=module -e "import { callPythonHelper } from './dist/computer-use/pythonBridge.js'; console.log(await callPythonHelper('list_displays', {}));"
```

## Repository Layout

```text
src/
  computer-use/
    executor.ts
    hostAdapter.ts
    pythonBridge.ts
  vendor/computer-use-mcp/
runtime/
  mac_helper.py
  requirements.txt
skill/
  computer-use-macos/
examples/
assets/
```

## Environment Flags

Optional knobs:

- `CLAUDE_COMPUTER_USE_DEBUG=1`
- `CLAUDE_COMPUTER_USE_COORDINATE_MODE=pixels`
- `CLAUDE_COMPUTER_USE_CLIPBOARD_PASTE=1`
- `CLAUDE_COMPUTER_USE_MOUSE_ANIMATION=1`
- `CLAUDE_COMPUTER_USE_HIDE_BEFORE_ACTION=0`

## Roadmap

- richer app-icon extraction without private APIs
- stronger app filtering for nested helper bundles
- broader automated MCP integration tests
- optional packaged release artifacts for easier distribution

## License

MIT

## Credits

This project preserves and adapts reusable TypeScript computer-use logic recovered from the Claude Code workflow, then replaces the missing private runtime with a fully standalone public macOS implementation.


path: project/README.zh-CN.md
content: <div align="center">
  <img src="./assets/hero.svg" alt="macos-computer-use-skill hero" width="100%" />
  <h1>macOS Computer-Use Skill</h1>
  <p><strong>一个面向 macOS 的顶级可移植 skill，内置独立 runtime 与 MCP server。</strong></p>
  <p>
    <a href="https://github.com/wimi321/macos-computer-use-skill">GitHub</a>
    ·
    <a href="https://clawhub.ai/wimi321/computer-use-macos">ClawHub</a>
    ·
    <a href="./README.md">English</a>
    ·
    <a href="./README.ja.md">日本語</a>
  </p>
</div>

## ClawHub 安装

这个 skill 已发布到 ClawHub，slug 是 [`computer-use-macos`](https://clawhub.ai/wimi321/computer-use-macos)。

```bash
clawhub install computer-use-macos
```

如果你还想拿到完整源码仓库，下面继续看 GitHub 方式。

## 项目定位

这个仓库本质上同时是：

- 一个顶级 `skill`
- 一套独立的 macOS runtime
- 一个给 agent 生态使用的 computer-use MCP server

它不只是给 Codex 用，skill 这种交付方式本身就是为了跨 agent 生态可移植。

## 这个项目解决什么问题

目标只有一个：

- 不依赖本机 Claude
- 不依赖私有 `.node` 二进制
- 不依赖“先从某个安装目录里捞内部资产”
- skill 装上之后就能把 computer-use 跑起来

这版仓库已经按这个目标重构完成。

## 你现在拿到的能力

- 顶级 macOS computer-use skill
- 独立 MCP server：截图、鼠标、键盘、应用启动、窗口/显示器信息、剪贴板
- 只使用公开依赖：`Node.js + Python + pyautogui + mss + Pillow + pyobjc`
- 首次运行自动自举：自动创建 `.runtime/venv` 并安装 Python 依赖
- 安装 skill 时会把完整项目一起复制到 `~/.codex/skills/computer-use-macos/project`
- 保留并复用提取出来的 TypeScript computer-use 工具链，但底层执行器已换成真正独立的 runtime

## 已完成的本地验证

已经在一台真实 macOS 机器上实际验证：

- runtime 自举成功
- 权限检测成功
- 显示器枚举成功
- 截图成功
- 前台应用识别成功
- 鼠标位置对应应用识别成功
- 窗口到显示器的归属解析成功
- 剪贴板读写成功
- 走 MCP `type` 工具链路的 GUI 输入 smoke test 成功
- MCP server 成功启动

## 0.2.2 修复了什么

这次真机测试里抓到了一个关键问题：在中文输入法环境下，普通英文文本如果按“逐键输入”执行，内容会被输入法状态污染，连纯 ASCII 都可能打错。

`0.2.2` 已修复这一点：在 macOS 上，只要具备剪贴板写权限，普通多字符文本会优先走剪贴板粘贴路径，而不是一键一键敲。这样 skill 安装后在真实中文工作环境里会稳定得多。

## 架构

```mermaid
flowchart LR
    A[Codex / MCP Client] --> B[macos-computer-use-skill]
    B --> C[提取出的 TypeScript MCP 工具层]
    B --> D[独立 Python bridge]
    D --> E[pyautogui]
    D --> F[mss + Pillow]
    D --> G[pyobjc Cocoa + Quartz]
    E --> H[鼠标 / 键盘]
    F --> I[截图]
    G --> J[应用 / 显示器 / 剪贴板 / 窗口]
```

## 安装

### 1. 克隆并安装 Node 依赖

```bash
git clone https://github.com/wimi321/macos-computer-use-skill.git
cd macos-computer-use-skill
npm install
npm run build
```

### 2. 启动 server

```bash
node dist/cli.js
```

首次启动时项目会自动：

- 创建 `.runtime/venv`
- 必要时自动补 `pip`
- 根据 `runtime/requirements.txt` 安装 Python 运行时依赖

不需要 Claude Desktop。也不需要任何私有 native 模块。

## MCP 配置

示例：

```json
{
  "mcpServers": {
    "computer-use": {
      "command": "node",
      "args": [
        "/absolute/path/to/macos-computer-use-skill/dist/cli.js"
      ],
      "env": {
        "CLAUDE_COMPUTER_USE_DEBUG": "0",
        "CLAUDE_COMPUTER_USE_COORDINATE_MODE": "pixels"
      }
    }
  }
}
```

参考 [`examples/mcp-config.json`](./examples/mcp-config.json)。

## Skill 安装

仓库自带顶级 skill：[`skill/computer-use-macos`](./skill/computer-use-macos)

你现在可以通过 ClawHub 直接安装，也可以从这个仓库本地安装。

### 方式 A：从 ClawHub 安装

```bash
clawhub install computer-use-macos
```

### 方式 B：从仓库安装

安装：

```bash
bash skill/computer-use-macos/scripts/install.sh
```

安装脚本会一起复制：

- skill 元数据
- 独立项目本体
- runtime 自举文件

安装后默认项目路径为：

```bash
~/.codex/skills/computer-use-macos/project
```

也就是说，即使原始 clone 被删掉，已安装的 skill 仍然有自己的可运行项目副本。

## 运行说明

### 权限

macOS 仍然需要：

- Accessibility
- Screen Recording

这个 standalone host 会在 MCP 流程中检测并反馈这两项状态。

### 截图过滤

当前 standalone runtime 声明的是 `screenshotFiltering: none`。

含义是：

- 截图本身不是 compositor 级过滤
- 但操作权限、allowlist、tier 限制仍由 MCP 层逻辑继续执行

### 平台范围

当前项目明确只支持 `macOS` 桌面 computer use，这一版还不是 Windows 或 Linux backend。

当前覆盖：

- 截图
- 鼠标控制
- 键盘输入
- 前台应用识别
- 已安装 / 运行中应用枚举
- 窗口到显示器映射
- 剪贴板访问
- 应用启动

## 验证矩阵

这次在本机完成的真实测试包括：

- `npm run check`
- `npm run build`
- `runtime/mac_helper.py` 的 Python 编译检查
- 权限探测：Accessibility 与 Screen Recording 均已授权
- 当前显示器枚举
- 真实桌面截图采集
- 运行中 / 已安装应用枚举
- 前台应用识别
- 将 skill 安装到全新 `CODEX_HOME`
- 在已安装 skill 的 `project/` 内执行 `npm install && npm run build`
- 通过 TextEdit 对 MCP `type` 工具做真实 GUI 输入回读校验，结果精确匹配

## 常用命令

```bash
npm run build
node dist/cli.js
```

```bash
node --input-type=module -e "import { callPythonHelper } from './dist/computer-use/pythonBridge.js'; console.log(await callPythonHelper('list_displays', {}));"
```

## 仓库结构

```text
src/
  computer-use/
    executor.ts
    hostAdapter.ts
    pythonBridge.ts
  vendor/computer-use-mcp/
runtime/
  mac_helper.py
  requirements.txt
skill/
  computer-use-macos/
examples/
assets/
```

## 可选环境变量

- `CLAUDE_COMPUTER_USE_DEBUG=1`
- `CLAUDE_COMPUTER_USE_COORDINATE_MODE=pixels`
- `CLAUDE_COMPUTER_USE_CLIPBOARD_PASTE=1`
- `CLAUDE_COMPUTER_USE_MOUSE_ANIMATION=1`
- `CLAUDE_COMPUTER_USE_HIDE_BEFORE_ACTION=0`

## 路线图

- 更好的 app icon 提取
- 更稳的嵌套 helper app 过滤
- 更完整的 MCP 集成测试
- 提供更易分发的打包产物

## License

MIT

## Credits

这个项目保留了从 Claude Code computer-use 工作流中提炼出来的可复用 TypeScript 逻辑，并用一套完全独立、公开可安装的 macOS runtime 替换了缺失的私有执行层。


path: project/assets/hero.svg
content: <svg width="1280" height="640" viewBox="0 0 1280 640" fill="none" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="bg" x1="54" y1="42" x2="1190" y2="582" gradientUnits="userSpaceOnUse">
      <stop stop-color="#0B1220"/>
      <stop offset="0.5" stop-color="#111C32"/>
      <stop offset="1" stop-color="#1D3557"/>
    </linearGradient>
    <linearGradient id="accent" x1="218" y1="116" x2="1112" y2="476" gradientUnits="userSpaceOnUse">
      <stop stop-color="#7EE787"/>
      <stop offset="0.45" stop-color="#5BC0EB"/>
      <stop offset="1" stop-color="#FFB703"/>
    </linearGradient>
    <linearGradient id="panel" x1="802" y1="157" x2="1107" y2="434" gradientUnits="userSpaceOnUse">
      <stop stop-color="#111927" stop-opacity="0.96"/>
      <stop offset="1" stop-color="#0B1220" stop-opacity="0.8"/>
    </linearGradient>
    <filter id="blur" x="0" y="0" width="1280" height="640" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
      <feGaussianBlur stdDeviation="60"/>
    </filter>
  </defs>

  <rect width="1280" height="640" rx="28" fill="url(#bg)"/>
  <g filter="url(#blur)" opacity="0.34">
    <circle cx="225" cy="147" r="132" fill="#7EE787"/>
    <circle cx="1022" cy="158" r="110" fill="#5BC0EB"/>
    <circle cx="952" cy="514" r="164" fill="#FFB703"/>
  </g>

  <rect x="62" y="58" width="1156" height="524" rx="26" fill="#0B1220" fill-opacity="0.25" stroke="rgba(255,255,255,0.12)"/>

  <g opacity="0.95">
    <rect x="94" y="92" width="394" height="34" rx="17" fill="#0F1728" stroke="#25324A"/>
    <circle cx="117" cy="109" r="5" fill="#FF6B6B"/>
    <circle cx="136" cy="109" r="5" fill="#FFD166"/>
    <circle cx="155" cy="109" r="5" fill="#7EE787"/>
    <text x="182" y="115" fill="#B9C6DA" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="14">macos-computer-use-skill</text>
  </g>

  <g>
    <text x="98" y="204" fill="white" font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="64" font-weight="700">Standalone</text>
    <text x="98" y="282" fill="url(#accent)" font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="70" font-weight="800">Computer Use</text>
    <text x="98" y="352" fill="#DFF5FF" font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="60" font-weight="760">Skill + MCP</text>
    <text x="98" y="404" fill="#D6E2F0" font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="21">Recovered from Claude Code and rebuilt as a portable public macOS runtime.</text>
    <text x="98" y="438" fill="#8EA3BC" font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="19">TS core, Python bridge, runtime bootstrap, and an installable top-level skill.</text>
  </g>

  <g>
    <rect x="96" y="482" width="160" height="44" rx="22" fill="#13233D" stroke="#2C4E7A"/>
    <text x="132" y="510" fill="#DCEBFF" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">stdio MCP</text>

    <rect x="272" y="482" width="204" height="44" rx="22" fill="#13233D" stroke="#2C4E7A"/>
    <text x="304" y="510" fill="#DCEBFF" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">macOS automation</text>

    <rect x="494" y="482" width="162" height="44" rx="22" fill="#13233D" stroke="#2C4E7A"/>
    <text x="526" y="510" fill="#DCEBFF" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">Codex skill</text>
  </g>

  <g>
    <rect x="770" y="134" width="370" height="336" rx="24" fill="url(#panel)" stroke="#314561"/>
    <text x="803" y="180" fill="#E8F0FB" font-family="ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif" font-size="22" font-weight="700">System Shape</text>

    <rect x="803" y="208" width="304" height="58" rx="16" fill="#122035" stroke="#29496F"/>
    <text x="827" y="243" fill="#DBE8F9" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">Codex / MCP client</text>

    <path d="M955 267V294" stroke="#5BC0EB" stroke-width="3" stroke-linecap="round"/>

    <rect x="803" y="296" width="304" height="58" rx="16" fill="#132840" stroke="#37618F"/>
    <text x="827" y="331" fill="#DBE8F9" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">macos-computer-use-skill</text>

    <path d="M955 355V382" stroke="#7EE787" stroke-width="3" stroke-linecap="round"/>

    <rect x="803" y="384" width="140" height="58" rx="16" fill="#132840" stroke="#37618F"/>
    <text x="826" y="419" fill="#DBE8F9" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">TS core</text>

    <rect x="968" y="384" width="139" height="58" rx="16" fill="#132840" stroke="#37618F"/>
    <text x="991" y="419" fill="#DBE8F9" font-family="SFMono-Regular, Menlo, Monaco, Consolas, monospace" font-size="18">py runtime</text>
  </g>
</svg>


path: project/dist/cli.d.ts
content: #!/usr/bin/env node
export {};


path: project/dist/cli.js
content: #!/usr/bin/env node
import { runServer } from './server.js';
import { errorMessage } from './lib/errors.js';
try {
    await runServer();
}
catch (error) {
    process.stderr.write(`macos-computer-use-skill failed: ${errorMessage(error)}\n`);
    process.exit(1);
}
//# sourceMappingURL=cli.js.map

path: project/dist/computer-use/appNames.d.ts
content: type InstalledAppLike = {
    readonly bundleId: string;
    readonly displayName: string;
    readonly path: string;
};
export declare function filterAppsForDescription(installed: readonly InstalledAppLike[], homeDir: string | undefined): string[];
export {};


path: project/dist/computer-use/appNames.js
content: const PATH_ALLOWLIST = [
    '/Applications/',
    '/System/Applications/',
];
const NAME_PATTERN_BLOCKLIST = [
    /Helper(?:$|\s\()/,
    /Agent(?:$|\s\()/,
    /Service(?:$|\s\()/,
    /Uninstaller(?:$|\s\()/,
    /Updater(?:$|\s\()/,
    /^\./,
];
const ALWAYS_KEEP_BUNDLE_IDS = new Set([
    'com.apple.Safari',
    'com.google.Chrome',
    'com.microsoft.edgemac',
    'org.mozilla.firefox',
    'company.thebrowser.Browser',
    'com.tinyspeck.slackmacgap',
    'us.zoom.xos',
    'com.microsoft.teams2',
    'com.microsoft.teams',
    'com.apple.MobileSMS',
    'com.apple.mail',
    'com.microsoft.Word',
    'com.microsoft.Excel',
    'com.microsoft.Powerpoint',
    'com.microsoft.Outlook',
    'com.apple.iWork.Pages',
    'com.apple.iWork.Numbers',
    'com.apple.iWork.Keynote',
    'notion.id',
    'com.apple.Notes',
    'md.obsidian',
    'com.linear',
    'com.figma.Desktop',
    'com.microsoft.VSCode',
    'com.apple.Terminal',
    'com.googlecode.iterm2',
    'com.github.GitHubDesktop',
    'com.apple.finder',
    'com.apple.iCal',
    'com.apple.systempreferences',
]);
const APP_NAME_ALLOWED = /^[\p{L}\p{M}\p{N}_ .&'()+-]+$/u;
const APP_NAME_MAX_LEN = 40;
const APP_NAME_MAX_COUNT = 50;
function isUserFacingPath(path, homeDir) {
    if (PATH_ALLOWLIST.some(root => path.startsWith(root)))
        return true;
    if (homeDir) {
        const userApps = homeDir.endsWith('/') ? `${homeDir}Applications/` : `${homeDir}/Applications/`;
        if (path.startsWith(userApps))
            return true;
    }
    return false;
}
function sanitizeCore(raw, applyCharFilter) {
    const seen = new Set();
    return raw
        .map(name => name.trim())
        .filter(trimmed => {
        if (!trimmed || trimmed.length > APP_NAME_MAX_LEN)
            return false;
        if (applyCharFilter && !APP_NAME_ALLOWED.test(trimmed))
            return false;
        if (seen.has(trimmed))
            return false;
        seen.add(trimmed);
        return true;
    })
        .sort((a, b) => a.localeCompare(b));
}
function sanitizeAppNames(raw) {
    const filtered = sanitizeCore(raw, true);
    if (filtered.length <= APP_NAME_MAX_COUNT)
        return filtered;
    return [
        ...filtered.slice(0, APP_NAME_MAX_COUNT),
        `... and ${filtered.length - APP_NAME_MAX_COUNT} more`,
    ];
}
export function filterAppsForDescription(installed, homeDir) {
    const alwaysKept = [];
    const rest = [];
    for (const app of installed) {
        if (ALWAYS_KEEP_BUNDLE_IDS.has(app.bundleId)) {
            alwaysKept.push(app.displayName);
            continue;
        }
        if (isUserFacingPath(app.path, homeDir) && !NAME_PATTERN_BLOCKLIST.some(re => re.test(app.displayName))) {
            rest.push(app.displayName);
        }
    }
    const sanitizedAlways = sanitizeCore(alwaysKept, false);
    const alwaysSet = new Set(sanitizedAlways);
    return [
        ...sanitizedAlways,
        ...sanitizeAppNames(rest).filter(name => !alwaysSet.has(name)),
    ];
}
//# sourceMappingURL=appNames.js.map

path: project/dist/computer-use/common.d.ts
content: export declare const COMPUTER_USE_MCP_SERVER_NAME = "computer-use";
export declare const CLI_HOST_BUNDLE_ID = "io.github.wimi321.macos-computer-use-skill";
export declare const CLI_CU_CAPABILITIES: {
    screenshotFiltering: "none";
    platform: "darwin";
};


path: project/dist/computer-use/common.js
content: export const COMPUTER_USE_MCP_SERVER_NAME = 'computer-use';
export const CLI_HOST_BUNDLE_ID = 'io.github.wimi321.macos-computer-use-skill';
export const CLI_CU_CAPABILITIES = {
    screenshotFiltering: 'none',
    platform: 'darwin',
};
//# sourceMappingURL=common.js.map

path: project/dist/computer-use/computerUseLock.d.ts
content: export type AcquireResult = {
    kind: 'acquired';
    fresh: boolean;
} | {
    kind: 'blocked';
    by: string;
};
export type CheckResult = {
    kind: 'free';
} | {
    kind: 'held_by_self';
} | {
    kind: 'blocked';
    by: string;
};
export declare function checkComputerUseLock(): Promise<CheckResult>;
export declare function tryAcquireComputerUseLock(): Promise<AcquireResult>;
export declare function releaseComputerUseLock(): Promise<boolean>;
export declare function isLockHeldLocally(): boolean;


path: project/dist/computer-use/computerUseLock.js
content: import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { homedir } from 'node:os';
import { getErrnoCode } from '../lib/errors.js';
import { logDebug } from '../lib/log.js';
const LOCK_FILENAME = 'computer-use.lock';
const CONFIG_DIR = join(homedir(), '.macos-computer-use-skill');
let heldLocally = false;
function getSessionId() {
    return process.env.CODEX_THREAD_ID ?? `pid-${process.pid}`;
}
function getLockPath() {
    return join(CONFIG_DIR, LOCK_FILENAME);
}
async function readLock() {
    try {
        const parsed = JSON.parse(await readFile(getLockPath(), 'utf8'));
        if (typeof parsed?.sessionId === 'string' && typeof parsed?.pid === 'number')
            return parsed;
    }
    catch { }
    return undefined;
}
function isProcessRunning(pid) {
    try {
        process.kill(pid, 0);
        return true;
    }
    catch {
        return false;
    }
}
async function tryCreateExclusive(lock) {
    try {
        await writeFile(getLockPath(), JSON.stringify(lock), { flag: 'wx' });
        return true;
    }
    catch (error) {
        if (getErrnoCode(error) === 'EEXIST')
            return false;
        throw error;
    }
}
export async function checkComputerUseLock() {
    const existing = await readLock();
    if (!existing)
        return { kind: 'free' };
    if (existing.sessionId === getSessionId())
        return { kind: 'held_by_self' };
    if (isProcessRunning(existing.pid))
        return { kind: 'blocked', by: existing.sessionId };
    await unlink(getLockPath()).catch(() => { });
    return { kind: 'free' };
}
export async function tryAcquireComputerUseLock() {
    const lock = { sessionId: getSessionId(), pid: process.pid };
    await mkdir(CONFIG_DIR, { recursive: true });
    if (await tryCreateExclusive(lock)) {
        heldLocally = true;
        return { kind: 'acquired', fresh: true };
    }
    const existing = await readLock();
    if (!existing) {
        await unlink(getLockPath()).catch(() => { });
        if (await tryCreateExclusive(lock)) {
            heldLocally = true;
            return { kind: 'acquired', fresh: true };
        }
        return { kind: 'blocked', by: 'unknown' };
    }
    if (existing.sessionId === lock.sessionId) {
        heldLocally = true;
        return { kind: 'acquired', fresh: false };
    }
    if (isProcessRunning(existing.pid))
        return { kind: 'blocked', by: existing.sessionId };
    logDebug('recovering stale computer-use lock from %s', existing.sessionId);
    await unlink(getLockPath()).catch(() => { });
    if (await tryCreateExclusive(lock)) {
        heldLocally = true;
        return { kind: 'acquired', fresh: true };
    }
    return { kind: 'blocked', by: 'unknown' };
}
export async function releaseComputerUseLock() {
    const existing = await readLock();
    heldLocally = false;
    if (!existing || existing.sessionId !== getSessionId())
        return false;
    try {
        await unlink(getLockPath());
        return true;
    }
    catch {
        return false;
    }
}
export function isLockHeldLocally() {
    return heldLocally;
}
//# sourceMappingURL=computerUseLock.js.map

path: project/dist/computer-use/drainRunLoop.d.ts
content: declare function retain(): void;
declare function release(): void;
export declare const retainPump: typeof retain;
export declare const releasePump: typeof release;
export declare function drainRunLoop<T>(fn: () => Promise<T>): Promise<T>;
export {};


path: project/dist/computer-use/drainRunLoop.js
content: import { logDebug } from '../lib/log.js';
import { withResolvers } from '../lib/withResolvers.js';
import { requireComputerUseSwift } from './swiftLoader.js';
let pump;
let pending = 0;
const TIMEOUT_MS = 30_000;
function retain() {
    pending += 1;
    if (!pump) {
        pump = setInterval(() => requireComputerUseSwift()._drainMainRunLoop(), 1);
        logDebug('drain run loop started');
    }
}
function release() {
    pending -= 1;
    if (pending <= 0 && pump) {
        clearInterval(pump);
        pump = undefined;
        pending = 0;
        logDebug('drain run loop stopped');
    }
}
export const retainPump = retain;
export const releasePump = release;
export async function drainRunLoop(fn) {
    retain();
    let timer;
    try {
        const work = fn();
        work.catch(() => { });
        const timeout = withResolvers();
        timer = setTimeout(() => timeout.reject(new Error(`computer-use native call exceeded ${TIMEOUT_MS}ms`)), TIMEOUT_MS);
        return await Promise.race([work, timeout.promise]);
    }
    finally {
        if (timer)
            clearTimeout(timer);
        release();
    }
}
//# sourceMappingURL=drainRunLoop.js.map

path: project/dist/computer-use/escHotkey.d.ts
content: export declare function registerEscHotkey(onEscape: () => void): boolean;
export declare function unregisterEscHotkey(): void;
export declare function notifyExpectedEscape(): void;


path: project/dist/computer-use/escHotkey.js
content: import { logDebug, logWarn } from '../lib/log.js';
import { releasePump, retainPump } from './drainRunLoop.js';
import { requireComputerUseSwift } from './swiftLoader.js';
let registered = false;
export function registerEscHotkey(onEscape) {
    if (registered)
        return true;
    const cu = requireComputerUseSwift();
    if (!cu.hotkey?.registerEscape?.(onEscape)) {
        logWarn('escape hotkey registration failed');
        return false;
    }
    retainPump();
    registered = true;
    logDebug('escape hotkey registered');
    return true;
}
export function unregisterEscHotkey() {
    if (!registered)
        return;
    try {
        requireComputerUseSwift().hotkey.unregister();
    }
    finally {
        releasePump();
        registered = false;
        logDebug('escape hotkey unregistered');
    }
}
export function notifyExpectedEscape() {
    if (!registered)
        return;
    requireComputerUseSwift().hotkey.notifyExpectedEscape();
}
//# sourceMappingURL=escHotkey.js.map

path: project/dist/computer-use/executor.d.ts
content: import type { ComputerExecutor } from '../vendor/computer-use-mcp/executor.js';
export declare function createCliExecutor(_opts: {
    getMouseAnimationEnabled: () => boolean;
    getHideBeforeActionEnabled: () => boolean;
}): ComputerExecutor;
export declare function unhideComputerUseApps(_bundleIds: readonly string[]): Promise<void>;


path: project/dist/computer-use/executor.js
content: import { API_RESIZE_PARAMS, targetImageSize } from '../vendor/computer-use-mcp/imageResize.js';
import { execFileNoThrow } from '../lib/execFileNoThrow.js';
import { sleep } from '../lib/sleep.js';
import { CLI_CU_CAPABILITIES, CLI_HOST_BUNDLE_ID } from './common.js';
import { callPythonHelper } from './pythonBridge.js';
const SCREENSHOT_JPEG_QUALITY = 0.75;
const MOVE_SETTLE_MS = 50;
function computeTargetDims(logicalW, logicalH, scaleFactor) {
    const physW = Math.round(logicalW * scaleFactor);
    const physH = Math.round(logicalH * scaleFactor);
    return targetImageSize(physW, physH, API_RESIZE_PARAMS);
}
function normalizeDisplayGeometry(display) {
    return {
        ...display,
        displayId: display.displayId ?? display.id,
        label: display.label ?? display.name,
    };
}
async function readClipboardViaPbpaste() {
    const { stdout, code } = await execFileNoThrow('pbpaste', [], { useCwd: false });
    if (code !== 0)
        throw new Error(`pbpaste exited with code ${code}`);
    return stdout;
}
async function writeClipboardViaPbcopy(text) {
    const { code } = await execFileNoThrow('pbcopy', [], { input: text, useCwd: false });
    if (code !== 0)
        throw new Error(`pbcopy exited with code ${code}`);
}
async function typeViaClipboard(text) {
    let saved;
    try {
        saved = await readClipboardViaPbpaste();
    }
    catch { }
    try {
        await writeClipboardViaPbcopy(text);
        await callPythonHelper('key', { keySequence: 'command+v', repeat: 1 });
        await sleep(100);
    }
    finally {
        if (typeof saved === 'string') {
            try {
                await writeClipboardViaPbcopy(saved);
            }
            catch { }
        }
    }
}
export function createCliExecutor(_opts) {
    if (process.platform !== 'darwin') {
        throw new Error(`createCliExecutor called on ${process.platform}. Computer control is macOS-only.`);
    }
    return {
        capabilities: {
            ...CLI_CU_CAPABILITIES,
            hostBundleId: CLI_HOST_BUNDLE_ID,
        },
        async prepareForAction() {
            return callPythonHelper('prepare_for_action', {});
        },
        async previewHideSet() {
            return callPythonHelper('preview_hide_set', {});
        },
        async getDisplaySize(displayId) {
            return normalizeDisplayGeometry(await callPythonHelper('get_display_size', { displayId }));
        },
        async listDisplays() {
            const displays = await callPythonHelper('list_displays', {});
            return displays.map(display => normalizeDisplayGeometry(display));
        },
        async findWindowDisplays(bundleIds) {
            return callPythonHelper('find_window_displays', { bundleIds });
        },
        async resolvePrepareCapture(opts) {
            const display = await this.getDisplaySize(opts.preferredDisplayId);
            const [targetW, targetH] = computeTargetDims(display.width, display.height, display.scaleFactor);
            const result = await callPythonHelper('resolve_prepare_capture', {
                preferredDisplayId: opts.preferredDisplayId,
                targetWidth: targetW,
                targetHeight: targetH,
                jpegQuality: SCREENSHOT_JPEG_QUALITY,
            });
            return {
                ...result,
                display: normalizeDisplayGeometry(result.display),
                resolvedDisplayId: result.resolvedDisplayId ?? result.displayId,
            };
        },
        async screenshot(opts) {
            const display = await this.getDisplaySize(opts.displayId);
            const [targetW, targetH] = computeTargetDims(display.width, display.height, display.scaleFactor);
            const result = await callPythonHelper('screenshot', {
                displayId: opts.displayId,
                targetWidth: targetW,
                targetHeight: targetH,
                jpegQuality: SCREENSHOT_JPEG_QUALITY,
            });
            return result;
        },
        async zoom(regionLogical, _allowedBundleIds, displayId) {
            const display = await this.getDisplaySize(displayId);
            const [outW, outH] = computeTargetDims(regionLogical.w, regionLogical.h, display.scaleFactor);
            return callPythonHelper('zoom', {
                x: regionLogical.x,
                y: regionLogical.y,
                width: regionLogical.w,
                height: regionLogical.h,
                targetWidth: outW,
                targetHeight: outH,
            });
        },
        async key(keySequence, repeat) {
            await callPythonHelper('key', { keySequence, repeat: repeat ?? 1 });
        },
        async holdKey(keyNames, durationMs) {
            await callPythonHelper('hold_key', { keyNames, durationMs });
        },
        async type(text, opts2) {
            if (opts2.viaClipboard) {
                await typeViaClipboard(text);
                return;
            }
            await callPythonHelper('type', { text });
        },
        readClipboard: readClipboardViaPbpaste,
        writeClipboard: writeClipboardViaPbcopy,
        async click(x, y, button, count, modifiers) {
            await callPythonHelper('click', { x, y, button, count, modifiers });
            await sleep(MOVE_SETTLE_MS);
        },
        async mouseDown() {
            await callPythonHelper('mouse_down', {});
        },
        async mouseUp() {
            await callPythonHelper('mouse_up', {});
        },
        async getCursorPosition() {
            return callPythonHelper('cursor_position', {});
        },
        async drag(from, to) {
            await callPythonHelper('drag', { from, to });
            await sleep(MOVE_SETTLE_MS);
        },
        async moveMouse(x, y) {
            await callPythonHelper('move_mouse', { x, y });
            await sleep(MOVE_SETTLE_MS);
        },
        async scroll(x, y, dx, dy) {
            await callPythonHelper('scroll', { x, y, deltaX: dx, deltaY: dy });
        },
        async getFrontmostApp() {
            return callPythonHelper('frontmost_app', {});
        },
        async appUnderPoint(x, y) {
            return callPythonHelper('app_under_point', { x, y });
        },
        async listInstalledApps() {
            return callPythonHelper('list_installed_apps', {});
        },
        async listRunningApps() {
            return callPythonHelper('list_running_apps', {});
        },
        async openApp(bundleId) {
            await callPythonHelper('open_app', { bundleId });
        },
    };
}
export async function unhideComputerUseApps(_bundleIds) {
    return;
}
//# sourceMappingURL=executor.js.map

path: project/dist/computer-use/gates.d.ts
content: import type { CoordinateMode, CuSubGates } from '../vendor/computer-use-mcp/types.js';
export declare function getChicagoEnabled(): boolean;
export declare function getChicagoCoordinateMode(): CoordinateMode;
export declare function getChicagoSubGates(): CuSubGates;


path: project/dist/computer-use/gates.js
content: const enabled = process.env.CLAUDE_COMPUTER_USE_ENABLED !== '0';
const coordinateMode = process.env.CLAUDE_COMPUTER_USE_COORDINATE_MODE ?? 'pixels';
export function getChicagoEnabled() {
    return enabled;
}
export function getChicagoCoordinateMode() {
    return coordinateMode;
}
export function getChicagoSubGates() {
    return {
        pixelValidation: process.env.CLAUDE_COMPUTER_USE_PIXEL_VALIDATION === '1',
        clipboardPasteMultiline: process.env.CLAUDE_COMPUTER_USE_CLIPBOARD_PASTE !== '0',
        mouseAnimation: process.env.CLAUDE_COMPUTER_USE_MOUSE_ANIMATION !== '0',
        hideBeforeAction: process.env.CLAUDE_COMPUTER_USE_HIDE_BEFORE_ACTION !== '0',
        autoTargetDisplay: process.env.CLAUDE_COMPUTER_USE_AUTO_TARGET_DISPLAY !== '0',
        clipboardGuard: process.env.CLAUDE_COMPUTER_USE_CLIPBOARD_GUARD !== '0',
    };
}
//# sourceMappingURL=gates.js.map

path: project/dist/computer-use/hostAdapter.d.ts
content: import type { ComputerUseHostAdapter } from '../vendor/computer-use-mcp/types.js';
export declare function getComputerUseHostAdapter(): ComputerUseHostAdapter;


path: project/dist/computer-use/hostAdapter.js
content: import { format } from 'node:util';
import { logDebug, logWarn } from '../lib/log.js';
import { COMPUTER_USE_MCP_SERVER_NAME } from './common.js';
import { createCliExecutor } from './executor.js';
import { getChicagoEnabled, getChicagoSubGates } from './gates.js';
import { callPythonHelper } from './pythonBridge.js';
class DebugLogger {
    silly(message, ...args) { logDebug(format(message, ...args)); }
    debug(message, ...args) { logDebug(format(message, ...args)); }
    info(message, ...args) { logDebug(format(message, ...args)); }
    warn(message, ...args) { logWarn(format(message, ...args)); }
    error(message, ...args) { logWarn(format(message, ...args)); }
}
let cached;
export function getComputerUseHostAdapter() {
    if (cached)
        return cached;
    cached = {
        serverName: COMPUTER_USE_MCP_SERVER_NAME,
        logger: new DebugLogger(),
        executor: createCliExecutor({
            getMouseAnimationEnabled: () => getChicagoSubGates().mouseAnimation,
            getHideBeforeActionEnabled: () => getChicagoSubGates().hideBeforeAction,
        }),
        ensureOsPermissions: async () => {
            const perms = await callPythonHelper('check_permissions', {});
            return perms.accessibility && perms.screenRecording
                ? { granted: true }
                : { granted: false, accessibility: perms.accessibility, screenRecording: perms.screenRecording };
        },
        isDisabled: () => !getChicagoEnabled(),
        getSubGates: getChicagoSubGates,
        getAutoUnhideEnabled: () => true,
        cropRawPatch: () => null,
    };
    return cached;
}
//# sourceMappingURL=hostAdapter.js.map

path: project/dist/computer-use/inputLoader.d.ts
content: export declare function requireComputerUseInput(): any;


path: project/dist/computer-use/inputLoader.js
content: import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
let cached;
export function requireComputerUseInput() {
    if (cached)
        return cached;
    throw new Error('Legacy native input loader was retained only for source-history reference. ' +
        'The standalone runtime now uses the Python bridge in src/computer-use/pythonBridge.ts instead.');
}
//# sourceMappingURL=inputLoader.js.map

path: project/dist/computer-use/pythonBridge.d.ts
content: export declare function callPythonHelper<T>(command: string, payload?: Record<string, unknown>): Promise<T>;
export declare function getRuntimePaths(): {
    projectRoot: string;
    runtimeRoot: string;
    venvRoot: string;
};


path: project/dist/computer-use/pythonBridge.js
content: import { createHash } from 'node:crypto';
import { readFile, mkdir, access, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { execFileNoThrow } from '../lib/execFileNoThrow.js';
import { logDebug } from '../lib/log.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, '../..');
const runtimeRoot = path.join(projectRoot, 'runtime');
const runtimeStateRoot = path.join(projectRoot, '.runtime');
const venvRoot = path.join(runtimeStateRoot, 'venv');
const requirementsPath = path.join(runtimeRoot, 'requirements.txt');
const helperPath = path.join(runtimeRoot, 'mac_helper.py');
const installStampPath = path.join(runtimeStateRoot, 'requirements.sha256');
let bootstrapPromise;
function pythonBinPath() {
    return path.join(venvRoot, 'bin', 'python3');
}
async function pathExists(target) {
    try {
        await access(target);
        return true;
    }
    catch {
        return false;
    }
}
async function runOrThrow(file, args, label) {
    const { code, stdout, stderr } = await execFileNoThrow(file, args, { useCwd: false });
    if (code !== 0) {
        throw new Error(`${label} failed with code ${code}: ${stderr || stdout || 'unknown error'}`);
    }
    return stdout;
}
async function ensureBootstrapped() {
    if (bootstrapPromise)
        return bootstrapPromise;
    bootstrapPromise = (async () => {
        await mkdir(runtimeStateRoot, { recursive: true });
        if (!(await pathExists(pythonBinPath()))) {
            logDebug('creating runtime venv at %s', venvRoot);
            await runOrThrow('python3', ['-m', 'venv', venvRoot], 'python venv creation');
        }
        if (!(await pathExists(path.join(venvRoot, 'bin', 'pip')))) {
            logDebug('bootstrapping pip with ensurepip');
            await runOrThrow(pythonBinPath(), ['-m', 'ensurepip', '--upgrade'], 'ensurepip');
        }
        const requirements = await readFile(requirementsPath, 'utf8');
        const digest = createHash('sha256').update(requirements).digest('hex');
        let installedDigest = '';
        try {
            installedDigest = (await readFile(installStampPath, 'utf8')).trim();
        }
        catch { }
        if (installedDigest !== digest) {
            logDebug('installing python runtime dependencies');
            await runOrThrow(pythonBinPath(), ['-m', 'pip', 'install', '--upgrade', 'pip'], 'pip upgrade');
            await runOrThrow(pythonBinPath(), ['-m', 'pip', 'install', '-r', requirementsPath], 'python dependency install');
            await writeFile(installStampPath, `${digest}\n`, 'utf8');
        }
    })();
    try {
        await bootstrapPromise;
    }
    catch (error) {
        bootstrapPromise = undefined;
        throw error;
    }
}
export async function callPythonHelper(command, payload = {}) {
    await ensureBootstrapped();
    const { code, stdout, stderr } = await execFileNoThrow(pythonBinPath(), [helperPath, command, '--payload', JSON.stringify(payload)], { useCwd: false });
    if (code !== 0 && !stdout.trim()) {
        throw new Error(stderr || `Python helper ${command} failed with code ${code}`);
    }
    let parsed;
    try {
        parsed = JSON.parse(stdout);
    }
    catch {
        throw new Error(stderr || stdout || `Python helper ${command} returned invalid JSON`);
    }
    if (!parsed.ok) {
        throw new Error(parsed.error?.message || `Python helper ${command} failed`);
    }
    return parsed.result;
}
export function getRuntimePaths() {
    return { projectRoot, runtimeRoot, venvRoot };
}
//# sourceMappingURL=pythonBridge.js.map

path: project/dist/computer-use/swiftLoader.d.ts
content: export declare function requireComputerUseSwift(): any;


path: project/dist/computer-use/swiftLoader.js
content: import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
let cached;
export function requireComputerUseSwift() {
    if (cached)
        return cached;
    throw new Error('Legacy native Swift loader was retained only for source-history reference. ' +
        'The standalone runtime now uses the Python bridge in src/computer-use/pythonBridge.ts instead.');
}
//# sourceMappingURL=swiftLoader.js.map

path: project/dist/lib/errors.d.ts
content: export declare function errorMessage(error: unknown): string;
export declare function getErrnoCode(error: unknown): string | undefined;


path: project/dist/lib/errors.js
content: export function errorMessage(error) {
    if (error instanceof Error)
        return error.message;
    return String(error);
}
export function getErrnoCode(error) {
    if (typeof error === 'object' && error !== null && 'code' in error) {
        const code = error.code;
        return typeof code === 'string' ? code : undefined;
    }
    return undefined;
}
//# sourceMappingURL=errors.js.map

path: project/dist/lib/execFileNoThrow.d.ts
content: export declare function execFileNoThrow(file: string, args: string[], options?: {
    input?: string;
    useCwd?: boolean;
}): Promise<{
    code: number;
    stdout: string;
    stderr: string;
}>;


path: project/dist/lib/execFileNoThrow.js
content: import { execFile } from 'node:child_process';
export function execFileNoThrow(file, args, options = {}) {
    return new Promise(resolve => {
        const child = execFile(file, args, {
            cwd: options.useCwd === false ? undefined : process.cwd(),
            encoding: 'utf8',
            maxBuffer: 32 * 1024 * 1024,
        }, (error, stdout, stderr) => {
            const code = typeof error?.code === 'number'
                ? error.code
                : error
                    ? 1
                    : 0;
            resolve({ code, stdout: stdout ?? '', stderr: stderr ?? '' });
        });
        if (options.input !== undefined) {
            child.stdin?.end(options.input);
        }
    });
}
//# sourceMappingURL=execFileNoThrow.js.map

path: project/dist/lib/log.d.ts
content: export declare function logDebug(message: string, ...args: unknown[]): void;
export declare function logWarn(message: string, ...args: unknown[]): void;


path: project/dist/lib/log.js
content: import { format } from 'node:util';
const enabled = process.env.CLAUDE_COMPUTER_USE_DEBUG === '1';
export function logDebug(message, ...args) {
    if (!enabled)
        return;
    process.stderr.write(`[claude-computer-use] ${format(message, ...args)}\n`);
}
export function logWarn(message, ...args) {
    process.stderr.write(`[claude-computer-use][warn] ${format(message, ...args)}\n`);
}
//# sourceMappingURL=log.js.map

path: project/dist/lib/sleep.d.ts
content: export declare function sleep(ms: number): Promise<void>;


path: project/dist/lib/sleep.js
content: export function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
//# sourceMappingURL=sleep.js.map

path: project/dist/lib/withResolvers.d.ts
content: export declare function withResolvers<T>(): {
    promise: Promise<T>;
    resolve: (value: T | PromiseLike<T>) => void;
    reject: (reason?: unknown) => void;
};


path: project/dist/lib/withResolvers.js
content: export function withResolvers() {
    let resolve;
    let reject;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
}
//# sourceMappingURL=withResolvers.js.map

path: project/dist/server.d.ts
content: export declare function runServer(): Promise<void>;


path: project/dist/server.js
content: import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { homedir } from 'node:os';
import { createComputerUseMcpServer } from './vendor/computer-use-mcp/mcpServer.js';
import { buildComputerUseTools } from './vendor/computer-use-mcp/tools.js';
import { filterAppsForDescription } from './computer-use/appNames.js';
import { getComputerUseHostAdapter } from './computer-use/hostAdapter.js';
import { getChicagoCoordinateMode } from './computer-use/gates.js';
import { createSessionContext } from './session.js';
import { logDebug } from './lib/log.js';
async function getInstalledAppNames() {
    try {
        const installed = await getComputerUseHostAdapter().executor.listInstalledApps();
        return filterAppsForDescription(installed, homedir());
    }
    catch {
        return undefined;
    }
}
export async function runServer() {
    const adapter = getComputerUseHostAdapter();
    const coordinateMode = getChicagoCoordinateMode();
    const context = createSessionContext();
    const server = createComputerUseMcpServer(adapter, coordinateMode, context);
    const installedAppNames = await getInstalledAppNames();
    const tools = buildComputerUseTools(adapter.executor.capabilities, coordinateMode, installedAppNames);
    server.setRequestHandler((await import('@modelcontextprotocol/sdk/types.js')).ListToolsRequestSchema, async () => adapter.isDisabled() ? { tools: [] } : { tools });
    const transport = new StdioServerTransport();
    logDebug('starting stdio MCP server');
    await server.connect(transport);
}
//# sourceMappingURL=server.js.map

path: project/dist/session.d.ts
content: import type { ComputerUseSessionContext } from './vendor/computer-use-mcp/types.js';
export declare function createSessionContext(): ComputerUseSessionContext;


path: project/dist/session.js
content: import { DEFAULT_GRANT_FLAGS } from './vendor/computer-use-mcp/types.js';
import { checkComputerUseLock, tryAcquireComputerUseLock } from './computer-use/computerUseLock.js';
function autoApprovePermission(req) {
    const granted = req.apps
        .filter(app => app.resolved && !app.alreadyGranted)
        .map(app => ({
        bundleId: app.resolved.bundleId,
        displayName: app.resolved.displayName,
        grantedAt: Date.now(),
        tier: app.proposedTier,
    }));
    const denied = req.apps
        .filter(app => !app.resolved)
        .map(app => ({
        bundleId: app.requestedName,
        reason: 'not_installed',
    }));
    return {
        granted,
        denied,
        flags: {
            ...DEFAULT_GRANT_FLAGS,
            ...req.requestedFlags,
        },
    };
}
export function createSessionContext() {
    const state = {
        allowedApps: [],
        grantFlags: { ...DEFAULT_GRANT_FLAGS },
        hiddenDuringTurn: new Set(),
    };
    return {
        getAllowedApps: () => state.allowedApps,
        getGrantFlags: () => state.grantFlags,
        getUserDeniedBundleIds: () => [],
        getSelectedDisplayId: () => state.selectedDisplayId,
        getDisplayPinnedByModel: () => state.displayPinnedByModel ?? false,
        getDisplayResolvedForApps: () => state.displayResolvedForApps,
        getLastScreenshotDims: () => state.lastScreenshotDims,
        onPermissionRequest: async (req) => autoApprovePermission(req),
        onAllowedAppsChanged: (apps, flags) => {
            state.allowedApps = [...apps];
            state.grantFlags = flags;
        },
        onAppsHidden: ids => {
            for (const id of ids)
                state.hiddenDuringTurn.add(id);
        },
        getClipboardStash: () => state.clipboardStash,
        onClipboardStashChanged: stash => {
            state.clipboardStash = stash;
        },
        onResolvedDisplayUpdated: displayId => {
            state.selectedDisplayId = displayId;
            state.displayPinnedByModel = false;
        },
        onDisplayPinned: displayId => {
            state.selectedDisplayId = displayId;
            state.displayPinnedByModel = displayId !== undefined;
            if (displayId === undefined)
                state.displayResolvedForApps = undefined;
        },
        onDisplayResolvedForApps: key => {
            state.displayResolvedForApps = key;
        },
        onScreenshotCaptured: dims => {
            state.lastScreenshotDims = dims;
        },
        checkCuLock: async () => {
            const result = await checkComputerUseLock();
            switch (result.kind) {
                case 'free':
                    return { holder: undefined, isSelf: false };
                case 'held_by_self':
                    return { holder: process.env.CODEX_THREAD_ID ?? `pid-${process.pid}`, isSelf: true };
                case 'blocked':
                    return { holder: result.by, isSelf: false };
            }
        },
        acquireCuLock: async () => {
            const result = await tryAcquireComputerUseLock();
            if (result.kind === 'blocked') {
                throw new Error(`Computer use is locked by another session (${result.by}).`);
            }
        },
        formatLockHeldMessage: holder => `Computer use is already in use by another session (${holder.slice(0, 8)}...).`,
    };
}
//# sourceMappingURL=session.js.map

path: project/dist/vendor/computer-use-mcp/deniedApps.d.ts
content: /**
 * App category lookup for tiered CU permissions. Three categories land at a
 * restricted tier instead of `"full"`:
 *
 *   - **browser** → `"read"` tier — visible in screenshots, NO interaction.
 *     The model can read an already-open page but must use the Claude-in-Chrome
 *     MCP for navigation/clicking/typing.
 *   - **terminal** → `"click"` tier — visible + clickable, NO typing. The
 *     model can click a Run button or scroll test output in an IDE, but can't
 *     type into the integrated terminal. Use the Bash tool for shell work.
 *   - **trading** → `"read"` tier — same restrictions as browsers, but no
 *     CiC-MCP alternative exists. For platforms where a stray click can
 *     execute a trade or send a message to a counterparty.
 *
 * Uncategorized apps default to `"full"`. See `getDefaultTierForApp`.
 *
 * Identification is two-layered:
 *   1. Bundle ID match (macOS-only; `InstalledApp.bundleId` is a
 *      CFBundleIdentifier and meaningless on Windows). Fast, exact, the
 *      primary mechanism while CU is darwin-gated.
 *   2. Display-name substring match (cross-platform fallback). Catches
 *      unresolved requests ("Chrome" when Chrome isn't installed) AND will
 *      be the primary mechanism on Windows/Linux where there's no bundle ID.
 *      Windows-relevant names (PowerShell, cmd, Windows Terminal) are
 *      included now so they activate the moment the darwin gate lifts.
 *
 * Keep this file **import-free** (like sentinelApps.ts) — the renderer may
 * import it via a package.json subpath export, and pulling in
 * `@modelcontextprotocol/sdk` (a devDep) through the index → mcpServer chain
 * would fail module resolution in Next.js. The `CuAppPermTier` type is
 * duplicated as a string literal below rather than imported.
 */
export type DeniedCategory = "browser" | "terminal" | "trading";
/**
 * Map a category to its hardcoded tier. Return-type is the string-literal
 * union inline (this file is import-free; see header comment). The
 * authoritative type is `CuAppPermTier` in types.ts — keep in sync.
 *
 * Not bijective — both `"browser"` and `"trading"` map to `"read"`. Copy
 * that differs by category (the "use CiC" hint is browser-only) must check
 * the category, not just the tier.
 */
export declare function categoryToTier(category: DeniedCategory | null): "read" | "click" | "full";
/**
 * Policy-level auto-deny. Unlike `userDeniedBundleIds` (per-user Settings
 * page), this is baked into the build. `buildAccessRequest` strips these
 * before the approval dialog with "blocked by policy" guidance; the agent
 * is told to not retry.
 */
export declare function isPolicyDenied(bundleId: string | undefined, displayName: string): boolean;
export declare function getDeniedCategory(bundleId: string): DeniedCategory | null;
/**
 * Display-name substring match. Called when bundle-ID resolution returned
 * nothing (`resolved === undefined`) or when no bundle-ID deny-list entry
 * matched. Returns the category for the first matching substring, or null.
 *
 * Case-insensitive, substring — so `"Google Chrome"`, `"chrome"`, and
 * `"Chrome Canary"` all match the `"chrome"` entry.
 */
export declare function getDeniedCategoryByDisplayName(name: string): DeniedCategory | null;
/**
 * Combined check — bundle ID first (exact, fast), then display-name
 * fallback. This is the function tool-call handlers should use.
 *
 * `bundleId` may be undefined (unresolved request — model asked for an app
 * that isn't installed or Spotlight didn't find). In that case only the
 * display-name check runs.
 */
export declare function getDeniedCategoryForApp(bundleId: string | undefined, displayName: string): DeniedCategory | null;
/**
 * Default tier for an app at grant time. Wraps `getDeniedCategoryForApp` +
 * `categoryToTier`. Browsers → `"read"`, terminals/IDEs → `"click"`,
 * everything else → `"full"`.
 *
 * Called by `buildAccessRequest` to populate `ResolvedAppRequest.proposedTier`
 * before the approval dialog shows.
 */
export declare function getDefaultTierForApp(bundleId: string | undefined, displayName: string): "read" | "click" | "full";
export declare const _test: {
    BROWSER_BUNDLE_IDS: ReadonlySet<string>;
    TERMINAL_BUNDLE_IDS: ReadonlySet<string>;
    TRADING_BUNDLE_IDS: ReadonlySet<string>;
    POLICY_DENIED_BUNDLE_IDS: ReadonlySet<string>;
    BROWSER_NAME_SUBSTRINGS: readonly string[];
    TERMINAL_NAME_SUBSTRINGS: readonly string[];
    TRADING_NAME_SUBSTRINGS: readonly string[];
    POLICY_DENIED_NAME_SUBSTRINGS: readonly string[];
};


path: project/dist/vendor/computer-use-mcp/executor.d.ts
content: export interface DisplayGeometry {
    id?: number;
    displayId?: number;
    width: number;
    height: number;
    scaleFactor: number;
    originX: number;
    originY: number;
    isPrimary?: boolean;
    name?: string;
    label?: string;
}
export interface ScreenshotResult {
    base64: string;
    width: number;
    height: number;
    displayWidth: number;
    displayHeight: number;
    displayId?: number;
    originX: number;
    originY: number;
}
export interface FrontmostApp {
    bundleId: string;
    displayName: string;
}
export interface RunningApp {
    bundleId: string;
    displayName: string;
}
export interface InstalledApp {
    bundleId: string;
    displayName: string;
    path: string;
    iconDataUrl?: string;
}
export interface ResolvePrepareCaptureResult extends ScreenshotResult {
    hidden: string[];
    display: DisplayGeometry;
    resolvedDisplayId?: number;
    captureError?: string;
}
export interface ComputerExecutor {
    capabilities: {
        screenshotFiltering: 'native' | 'none';
        platform: 'darwin' | 'win32';
        hostBundleId: string;
        teachMode?: boolean;
    };
    prepareForAction(allowlistBundleIds: string[], displayId?: number): Promise<string[]>;
    previewHideSet(allowlistBundleIds: string[], displayId?: number): Promise<Array<{
        bundleId: string;
        displayName: string;
    }>>;
    getDisplaySize(displayId?: number): Promise<DisplayGeometry>;
    listDisplays(): Promise<DisplayGeometry[]>;
    findWindowDisplays(bundleIds: string[]): Promise<Array<{
        bundleId: string;
        displayIds: number[];
    }>>;
    resolvePrepareCapture(opts: {
        allowedBundleIds: string[];
        preferredDisplayId?: number;
        autoResolve: boolean;
        doHide?: boolean;
    }): Promise<ResolvePrepareCaptureResult>;
    screenshot(opts: {
        allowedBundleIds: string[];
        displayId?: number;
    }): Promise<ScreenshotResult>;
    zoom(regionLogical: {
        x: number;
        y: number;
        w: number;
        h: number;
    }, allowedBundleIds: string[], displayId?: number): Promise<{
        base64: string;
        width: number;
        height: number;
    }>;
    key(keySequence: string, repeat?: number): Promise<void>;
    holdKey(keyNames: string[], durationMs: number): Promise<void>;
    type(text: string, opts: {
        viaClipboard: boolean;
    }): Promise<void>;
    click(x: number, y: number, button: 'left' | 'right' | 'middle', count: 1 | 2 | 3, modifiers?: string[]): Promise<void>;
    drag(from: {
        x: number;
        y: number;
    } | undefined, to: {
        x: number;
        y: number;
    }): Promise<void>;
    moveMouse(x: number, y: number): Promise<void>;
    scroll(x: number, y: number, deltaX: number, deltaY: number): Promise<void>;
    mouseDown(): Promise<void>;
    mouseUp(): Promise<void>;
    getCursorPosition(): Promise<{
        x: number;
        y: number;
    }>;
    getFrontmostApp(): Promise<FrontmostApp | null>;
    appUnderPoint(x: number, y: number): Promise<{
        bundleId: string;
        displayName: string;
    } | null>;
    listInstalledApps(): Promise<InstalledApp[]>;
    getAppIcon?(path: string): Promise<string | undefined>;
    listRunningApps(): Promise<RunningApp[]>;
    openApp(bundleId: string): Promise<void>;
    readClipboard(): Promise<string>;
    writeClipboard(text: string): Promise<void>;
}


path: project/dist/vendor/computer-use-mcp/executor.js
content: export {};
//# sourceMappingURL=executor.js.map

path: project/dist/vendor/computer-use-mcp/subGates.d.ts
content: import type { CuSubGates } from './types.js';
export declare const ALL_SUB_GATES_ON: CuSubGates;
export declare const ALL_SUB_GATES_OFF: CuSubGates;


path: project/dist/vendor/computer-use-mcp/types.js
content: export const DEFAULT_GRANT_FLAGS = {
    clipboardRead: false,
    clipboardWrite: false,
    systemKeyCombos: false,
};
//# sourceMappingURL=types.js.map

path: project/runtime/requirements.txt
content: mss>=10.1.0
Pillow>=11.3.0
pyautogui>=0.9.54
pyobjc-core>=11.1
pyobjc-framework-Cocoa>=11.1
pyobjc-framework-Quartz>=11.1


path: project/src/lib/sleep.ts
content: export function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}


Security scan verdicts — ClawScan: suspicious | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)