From 9ebb2f89d5183e40af636a9b0b11e606c310d531 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 3 Jun 2026 09:34:12 +0000 Subject: [PATCH] Add iTerm2 support via Python AutoLaunch script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - iterm2/paws.py: ~60-line Python script using the iTerm2 Python API; registers paws_toggle() (Cmd+G), paws_picker() (Cmd+Shift+P), paws_help() (Cmd+H) as RPC functions. Tab state persisted to ~/.config/paws/iterm2-tab-id so toggle survives config reloads. - docs/iterm2-setup.md: step-by-step setup guide (copy script, reload, bind 3 keys in Settings → Keys → Key Bindings) - README/README.zh: badge updated to "Kaku | WezTerm | iTerm2"; install section shows all three terminal paths - SKILL.md: preconditions include iTerm2; Step 2 split into 2a (Lua) and 2b (Python script for iTerm2) iTerm2 requires one extra step vs WezTerm (manual key binding in GUI) but runtime behavior is identical once set up. https://claude.ai/code/session_017EVNoc8mhrztPNg2S1QEA3 --- README.md | 9 +-- README.zh.md | 9 +-- docs/iterm2-setup.md | 82 ++++++++++++++++++++++++++ iterm2/paws.py | 111 +++++++++++++++++++++++++++++++++++ skills/paws-install/SKILL.md | 33 ++++++++--- 5 files changed, 229 insertions(+), 15 deletions(-) create mode 100644 docs/iterm2-setup.md create mode 100644 iterm2/paws.py diff --git a/README.md b/README.md index ba8a106..88ed39f 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ English | [中文](README.zh.md) # 🐾 Paws -[![CI](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml/badge.svg)](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Works with Kaku & WezTerm](https://img.shields.io/badge/Works_with-Kaku_%26_WezTerm-blue)](https://wezfurlong.org/wezterm/) [![Made with Lua & Rust](https://img.shields.io/badge/Made_with-Lua_&_Rust-orange)]() [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/interesting-vibe-coding/paws/pulls) [![GitHub Stars](https://img.shields.io/github/stars/interesting-vibe-coding/paws?style=flat&color=yellow)](https://github.com/interesting-vibe-coding/paws/stargazers) +[![CI](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml/badge.svg)](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Works with Kaku, WezTerm & iTerm2](https://img.shields.io/badge/Works_with-Kaku_%7C_WezTerm_%7C_iTerm2-blue)](https://github.com/interesting-vibe-coding/paws/blob/main/docs/iterm2-setup.md) [![Made with Lua & Rust](https://img.shields.io/badge/Made_with-Lua_&_Rust-orange)]() [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/interesting-vibe-coding/paws/pulls) [![GitHub Stars](https://img.shields.io/github/stars/interesting-vibe-coding/paws?style=flat&color=yellow)](https://github.com/interesting-vibe-coding/paws/stargazers) Play games while your AI agent works. A status HUD tells you when to come back. @@ -50,9 +50,10 @@ cargo install --git https://github.com/interesting-vibe-coding/paws-games --bin cargo install --git https://github.com/interesting-vibe-coding/paws-games --bin tetris ``` -Then add [`lua/paws.lua`](lua/paws.lua) to your terminal config (before `return config`) and wire hooks for your agent (see [`hooks/`](hooks/) for reference configs). -- **Kaku:** `~/.config/kaku/kaku.lua` — reload with CMD+Shift+R -- **WezTerm:** `~/.config/wezterm/wezterm.lua` — auto-reloads on save +Then wire the terminal integration and hooks for your agent (see [`hooks/`](hooks/) for reference configs). +- **Kaku:** add [`lua/paws.lua`](lua/paws.lua) to `~/.config/kaku/kaku.lua` before `return config` — reload with CMD+Shift+R +- **WezTerm:** add [`lua/paws.lua`](lua/paws.lua) to `~/.config/wezterm/wezterm.lua` — auto-reloads on save +- **iTerm2:** copy [`iterm2/paws.py`](iterm2/paws.py) to `~/.config/iterm2/scripts/AutoLaunch/` and bind 3 keys — see [docs/iterm2-setup.md](docs/iterm2-setup.md) ## Games diff --git a/README.zh.md b/README.zh.md index 954a725..c27ef8b 100644 --- a/README.zh.md +++ b/README.zh.md @@ -4,7 +4,7 @@ # 🐾 Paws -[![CI](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml/badge.svg)](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Works with Kaku & WezTerm](https://img.shields.io/badge/Works_with-Kaku_%26_WezTerm-blue)](https://wezfurlong.org/wezterm/) [![Made with Lua & Rust](https://img.shields.io/badge/Made_with-Lua_&_Rust-orange)]() [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/interesting-vibe-coding/paws/pulls) [![GitHub Stars](https://img.shields.io/github/stars/interesting-vibe-coding/paws?style=flat&color=yellow)](https://github.com/interesting-vibe-coding/paws/stargazers) +[![CI](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml/badge.svg)](https://github.com/interesting-vibe-coding/paws/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Works with Kaku, WezTerm & iTerm2](https://img.shields.io/badge/Works_with-Kaku_%7C_WezTerm_%7C_iTerm2-blue)](https://github.com/interesting-vibe-coding/paws/blob/main/docs/iterm2-setup.md) [![Made with Lua & Rust](https://img.shields.io/badge/Made_with-Lua_&_Rust-orange)]() [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/interesting-vibe-coding/paws/pulls) [![GitHub Stars](https://img.shields.io/github/stars/interesting-vibe-coding/paws?style=flat&color=yellow)](https://github.com/interesting-vibe-coding/paws/stargazers) Agent 工作时尽情玩,需要你时一眼就看到。 @@ -50,9 +50,10 @@ cargo install --git https://github.com/interesting-vibe-coding/paws-games --bin cargo install --git https://github.com/interesting-vibe-coding/paws-games --bin tetris ``` -然后将 [`lua/paws.lua`](lua/paws.lua) 添加到终端配置中(`return config` 之前),并为你的 Agent 配置 hooks(参考 [`hooks/`](hooks/) 目录)。 -- **Kaku:** `~/.config/kaku/kaku.lua` — 重载需按 CMD+Shift+R -- **WezTerm:** `~/.config/wezterm/wezterm.lua` — 保存后自动重载 +然后配置终端集成,并为你的 Agent 配置 hooks(参考 [`hooks/`](hooks/) 目录)。 +- **Kaku:** 将 [`lua/paws.lua`](lua/paws.lua) 添加到 `~/.config/kaku/kaku.lua` 的 `return config` 之前 — 重载需按 CMD+Shift+R +- **WezTerm:** 将 [`lua/paws.lua`](lua/paws.lua) 添加到 `~/.config/wezterm/wezterm.lua` — 保存后自动重载 +- **iTerm2:** 将 [`iterm2/paws.py`](iterm2/paws.py) 复制到 `~/.config/iterm2/scripts/AutoLaunch/`,然后绑定 3 个快捷键 — 详见 [docs/iterm2-setup.md](docs/iterm2-setup.md) ## 游戏 diff --git a/docs/iterm2-setup.md b/docs/iterm2-setup.md new file mode 100644 index 0000000..c7dcc04 --- /dev/null +++ b/docs/iterm2-setup.md @@ -0,0 +1,82 @@ +# Paws × iTerm2 setup + +Paws works in iTerm2 via a small Python AutoLaunch script that registers three +keyboard shortcuts. The `paws` binary itself runs identically in any terminal — +this script handles the tab-create/toggle behavior that makes the workflow smooth. + +## What you get + +| Key | Action | +|-----|--------| +| **Cmd+G** | First press: open the game picker. After that: toggle agent ↔ game. | +| **Cmd+Shift+P** | Close the game tab and re-open the picker. | +| **Cmd+H** | Open the Paws repo in your browser. | + +## Install + +### 1. Copy the script + +```bash +mkdir -p ~/.config/iterm2/scripts/AutoLaunch +cp iterm2/paws.py ~/.config/iterm2/scripts/AutoLaunch/paws.py +``` + +iTerm2 automatically runs scripts in `AutoLaunch/` at startup. The script +registers the three RPC functions above and keeps running in the background. + +### 2. Reload scripts + +In iTerm2: **Scripts → AutoLaunch → paws.py** (or restart iTerm2). + +You only need to do this once — after that it auto-starts on every launch. + +### 3. Bind the three keys + +Open **iTerm2 → Settings → Keys → Key Bindings**, then click **+** three times: + +| Keyboard Shortcut | Action | Parameter | +|-------------------|--------|-----------| +| `Cmd+G` | Invoke Script Function | `paws_toggle()` | +| `Cmd+Shift+P` | Invoke Script Function | `paws_picker()` | +| `Cmd+H` | Invoke Script Function | `paws_help()` | + +For each binding: click **+** → set the key combo → choose **"Invoke Script Function"** from the Action drop-down → type the function name exactly as shown above. + +> **Note:** "Invoke Script Function" only appears in the Action list once the +> script is loaded. Reload first (step 2) if you don't see it. + +### 4. Done + +Press **Cmd+G** — the game picker appears in a new tab. The HUD on the top row +shows your agent sessions. Press **Cmd+G** again to toggle back. + +## How it works + +The script (`iterm2/paws.py`) uses the [iTerm2 Python API](https://iterm2.com/python-api/): + +- On `paws_toggle()`: looks for an existing paws tab (by ID, persisted to + `~/.config/paws/iterm2-tab-id`). If none exists, spawns a new tab running + `paws` via a login shell (so `~/.cargo/bin` is on PATH). If you're already on + the paws tab, switches back to the previous tab. +- On `paws_picker()`: closes the existing paws tab and opens a fresh one. +- On `paws_help()`: calls `open` to launch the repo URL. + +## Troubleshooting + +**"Invoke Script Function" not in the Action list** +The script isn't loaded yet. Go to **Scripts → AutoLaunch → paws.py** to +start it, then try the key binding dialog again. + +**`paws: command not found`** +Run `cargo install --path .` from the paws repo, then open a new terminal +tab and try again. + +**State mismatch after closing the game tab manually** +Delete `~/.config/paws/iterm2-tab-id` — Paws will treat the next Cmd+G as +a fresh start. + +## Difference vs WezTerm / Kaku + +iTerm2 setup requires one extra step: wiring keybindings in the GUI (3 clicks +per binding). WezTerm/Kaku uses a Lua config file where Paws ships the bindings +as code. The runtime behavior is identical once set up. diff --git a/iterm2/paws.py b/iterm2/paws.py new file mode 100644 index 0000000..56914bf --- /dev/null +++ b/iterm2/paws.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Paws 🐾 — iTerm2 integration. + +Install +------- + cp iterm2/paws.py ~/.config/iterm2/scripts/AutoLaunch/paws.py + chmod +x ~/.config/iterm2/scripts/AutoLaunch/paws.py + +Then bind three keys in iTerm2 → Settings → Keys → Key Bindings (+): + Cmd+G → Invoke Script Function → paws_toggle() + Cmd+Shift+P → Invoke Script Function → paws_picker() + Cmd+H → Invoke Script Function → paws_help() + +iTerm2 auto-starts this script at launch. State (current paws tab ID) is kept +in ~/.config/paws/iterm2-tab-id so it survives config reloads. +""" + +import iterm2 +import os +import subprocess + +_STATE_FILE = os.path.expanduser("~/.config/paws/iterm2-tab-id") + + +def _read_tab_id() -> str | None: + try: + return open(_STATE_FILE).read().strip() or None + except FileNotFoundError: + return None + + +def _save_tab_id(tab_id: str) -> None: + os.makedirs(os.path.dirname(_STATE_FILE), exist_ok=True) + open(_STATE_FILE, "w").write(tab_id) + + +def _clear_tab_id() -> None: + try: + os.remove(_STATE_FILE) + except FileNotFoundError: + pass + + +async def _find_paws_tab(window, tab_id: str | None): + if tab_id is None: + return None + for tab in window.tabs: + if tab.tab_id == tab_id: + return tab + return None + + +async def _spawn_paws(window) -> None: + shell = os.environ.get("SHELL", "/bin/zsh") + tab = await window.async_create_tab() + # Login shell so ~/.cargo/bin is on PATH + await tab.current_session.async_send_text(f"{shell} -l -c paws\n") + _save_tab_id(tab.tab_id) + + +async def main(connection): + app = await iterm2.async_get_app(connection) + + @iterm2.RPC + async def paws_toggle(window_id=iterm2.Reference("id")): + """Cmd+G — open game picker, or toggle between agent and game.""" + window = app.get_window_by_id(window_id) + if not window: + return + + paws_tab = await _find_paws_tab(window, _read_tab_id()) + + if paws_tab is None: + await _spawn_paws(window) + elif window.current_tab.tab_id == paws_tab.tab_id: + # On paws tab → switch back to the previous tab + tabs = window.tabs + idx = next(i for i, t in enumerate(tabs) if t.tab_id == paws_tab.tab_id) + prev_idx = idx - 1 if idx > 0 else (1 if len(tabs) > 1 else None) + if prev_idx is not None: + await tabs[prev_idx].async_select() + else: + await paws_tab.async_select() + + await paws_toggle.async_register(connection, paws_toggle) + + @iterm2.RPC + async def paws_picker(window_id=iterm2.Reference("id")): + """Cmd+Shift+P — close the game tab and reopen the picker.""" + window = app.get_window_by_id(window_id) + if not window: + return + + paws_tab = await _find_paws_tab(window, _read_tab_id()) + if paws_tab: + await paws_tab.async_close(force=True) + _clear_tab_id() + await _spawn_paws(window) + + await paws_picker.async_register(connection, paws_picker) + + @iterm2.RPC + async def paws_help(window_id=iterm2.Reference("id")): + """Cmd+H — open the Paws repo in your browser.""" + subprocess.run(["open", "https://github.com/interesting-vibe-coding/paws"]) + + await paws_help.async_register(connection, paws_help) + + +iterm2.run_forever(main) diff --git a/skills/paws-install/SKILL.md b/skills/paws-install/SKILL.md index 56d5bbe..815f903 100644 --- a/skills/paws-install/SKILL.md +++ b/skills/paws-install/SKILL.md @@ -11,12 +11,13 @@ You are installing Paws for the user. Work from a local clone of this repo ## 0. Preconditions -- Confirm the terminal is **Kaku** (`which kaku`) or **WezTerm** (`which wezterm`). - Both are supported — the same `lua/paws.lua` works in both without modification. - If neither is installed, ask the user to install one first and stop. -- Determine the terminal type — it affects the config path in Step 2: - - Kaku: `~/.config/kaku/kaku.lua` - - WezTerm: `~/.config/wezterm/wezterm.lua` +- Confirm the terminal is **Kaku** (`which kaku`), **WezTerm** (`which wezterm`), + or **iTerm2** (`ls /Applications/iTerm.app`). All three are supported. + If none is installed, ask the user to install one first and stop. +- Determine the terminal type — it affects Step 2: + - Kaku: `~/.config/kaku/kaku.lua` (Lua config) + - WezTerm: `~/.config/wezterm/wezterm.lua` (Lua config) + - iTerm2: Python script + manual key bindings (see Step 2b) - Note the repo root (absolute path) — you'll need it for hook paths. **All hook paths in config files must be absolute** — `~` is not expanded by any of the three agents. @@ -48,7 +49,7 @@ Any games not installed now can be installed later directly from the in-app game picker (uninstalled entries show "⤓ install" and run the install command on Enter). -## 2. Merge the Lua into the terminal config +## 2a. Merge the Lua into the terminal config (Kaku / WezTerm) The terminal config returns a `config` table at the end. The snippet to insert is `lua/paws.lua` from this repo — it works identically in Kaku and WezTerm. @@ -65,6 +66,24 @@ Steps: add `config.keys = config.keys or {}` at the top of the inserted block. - Syntax-check afterward: `luac -p ` (if `luac` exists). +## 2b. Install the Python script (iTerm2 only) + +Skip this step if the user is on Kaku or WezTerm. + +```bash +mkdir -p ~/.config/iterm2/scripts/AutoLaunch +cp /iterm2/paws.py ~/.config/iterm2/scripts/AutoLaunch/paws.py +``` + +Then tell the user to: +1. In iTerm2: **Scripts → AutoLaunch → paws.py** to load the script. +2. Open **Settings → Keys → Key Bindings** and add three bindings: + - `Cmd+G` → Invoke Script Function → `paws_toggle()` + - `Cmd+Shift+P` → Invoke Script Function → `paws_picker()` + - `Cmd+H` → Invoke Script Function → `paws_help()` + +Full details: [docs/iterm2-setup.md](../../docs/iterm2-setup.md) + ## 3. Wire the agent's state signals (for the status HUD) Paws ships two hook scripts that write session state to `/tmp/paws-sessions/`