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
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand Down
9 changes: 5 additions & 4 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 工作时尽情玩,需要你时一眼就看到。

Expand Down Expand Up @@ -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)

## 游戏

Expand Down
82 changes: 82 additions & 0 deletions docs/iterm2-setup.md
Original file line number Diff line number Diff line change
@@ -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.
111 changes: 111 additions & 0 deletions iterm2/paws.py
Original file line number Diff line number Diff line change
@@ -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)
33 changes: 26 additions & 7 deletions skills/paws-install/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -65,6 +66,24 @@ Steps:
add `config.keys = config.keys or {}` at the top of the inserted block.
- Syntax-check afterward: `luac -p <config-path>` (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 <REPO>/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/<id>`
Expand Down
Loading