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
92 changes: 92 additions & 0 deletions .agents/skills/run/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: run
description: Launch PickScribe in dev mode or a sandboxed headless lab profile when asked to run the app, screenshot it, or confirm a change works in the real app.
---

## Normal dev launch

Use only in an empty desktop session you own. The real identifier is `com.pickforge.pickscribe`; a plain launch contacts any running instance and focuses its window.
```bash
bun install --frozen-lockfile
bun run tauri dev
```
There is no codegen or sidecar build. It needs Bun, Rust, GTK3, WebKit2GTK 4.1, libayatana-appindicator, librsvg, OpenSSL; audio also needs whisper.cpp and system tools.

## Isolated lab launch

Each lab starts from an empty, safe profile; anything outside HOME/XDG isolation (inherited environment variables, portals, and system services) can still touch real state.
One lab per checkout: state lives in `$PWD/.lab.env`, so concurrent labs need separate worktrees. Prepare the profile:
```bash
set -e
[ ! -f "$PWD/.lab.env" ] || { echo "lab state exists — clean up the previous lab first" >&2; false; }
bun install --frozen-lockfile
command -v ss >/dev/null || { echo "ss (iproute2) required" >&2; false; }
REAL_HOME=$HOME; LAB_HOME=$(mktemp -d /tmp/pickscribe-lab-home.XXXX)
mkdir -m 700 "$LAB_HOME/runtime"
for n in $(seq 90 120); do [ ! -e "/tmp/.X11-unix/X$n" ] && DISPLAY_NUM=$n && break; done
: "${DISPLAY_NUM:?No free X display in 90-120}"
for candidate in {1421..1439}; do ! ss -ltnH "sport = :$candidate" | grep -q . && { PORT=$candidate; break; }; done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fail when the port scanner is unavailable

In minimal agent images where ss/iproute2 is not installed, this negated pipeline treats the ss: command not found case as “no listener” because grep sees no input, so it blindly selects 1421 instead of failing or using another probe. If that port is already occupied, the subsequent bun run dev -- --port "$PORT" --strictPort exits and the lab never starts, even though the workflow claims it scanned for a free port.

Useful? React with 👍 / 👎.

: "${PORT:?No free port in 1421-1439}"
mkdir -p "$LAB_HOME/.config/pickscribe"
printf '%s\n' '[general]' 'local_only = true' '' '[paste]' 'method = "none"' 'copy_to_clipboard = false' > "$LAB_HOME/.config/pickscribe/config.toml"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid clipboard-only mode in the lab seed config

For lab runs that exercise dictation, this seed does not actually disable output: method = "none" is the app's Clipboard-only mode, and the GUI path calls paste::deliver, which copies whenever cfg.method != "type" even if copy_to_clipboard is false (src/engine/paste.rs:53). Because the lab launch keeps the user's runtime/PATH while only unsetting WAYLAND_DISPLAY, environments with a clipboard helper such as wl-copy can still write dictated text to the live clipboard, contradicting the safe-profile/paste-through guidance; the skill needs a true no-copy/no-paste path or an explicit warning not to test dictation this way.

Useful? React with 👍 / 👎.

printf 'REAL_HOME=%s\nPORT=%s\nDISPLAY_NUM=%s\nLAB_HOME=%s\n' "$REAL_HOME" "$PORT" "$DISPLAY_NUM" "$LAB_HOME" > "$PWD/.lab.env"
```
Start Vite, Xvfb, and the lab. Wait for Xvfb before starting xfwm4 or the app:
```bash
source "$PWD/.lab.env"
set -e
setsid bun run dev -- --port "$PORT" --strictPort </dev/null >"/tmp/pickscribe-vite-$PORT.log" 2>&1 &
for _ in {1..60}; do curl -fsS "http://127.0.0.1:$PORT/" >/dev/null 2>&1 && break; sleep 1; done
curl -fsS "http://127.0.0.1:$PORT/" >/dev/null || { echo "Vite never became ready on :$PORT" >&2; false; }
setsid Xvfb ":$DISPLAY_NUM" -screen 0 1440x1000x24 -nolisten tcp </dev/null >"/tmp/pickscribe-xvfb-$DISPLAY_NUM.log" 2>&1 &
for _ in {1..50}; do [ -e "/tmp/.X11-unix/X$DISPLAY_NUM" ] && break; sleep 0.1; done
[ -e "/tmp/.X11-unix/X$DISPLAY_NUM" ] || { echo "Xvfb did not start" >&2; false; }
setsid xfwm4 --display=":$DISPLAY_NUM" --compositor=off </dev/null >"/tmp/pickscribe-xfwm-$DISPLAY_NUM.log" 2>&1 &
setsid dbus-run-session -- env -u WAYLAND_DISPLAY -u DEEPSEEK_API_KEY -u OPENAI_API_KEY -u OLLAMA_API_KEY -u PICKSCRIBE_API_KEY GDK_BACKEND=x11 DISPLAY=":$DISPLAY_NUM" \
HOME="$LAB_HOME" XDG_CONFIG_HOME="$LAB_HOME/.config" XDG_DATA_HOME="$LAB_HOME/.local/share" XDG_CACHE_HOME="$LAB_HOME/.cache" \
XDG_RUNTIME_DIR="$LAB_HOME/runtime" PICKSCRIBE_STATE_DIR="$LAB_HOME/state" CARGO_HOME="$REAL_HOME/.cargo" RUSTUP_HOME="$REAL_HOME/.rustup" \
bun run tauri dev --config '{"identifier":"com.pickforge.pickscribe.labtest'"$DISPLAY_NUM"'","build":{"devUrl":"http://127.0.0.1:'"$PORT"'","beforeDevCommand":""}}' </dev/null >"/tmp/pickscribe-lab-$DISPLAY_NUM.log" 2>&1 &
```
GDK otherwise prefers Wayland: `DISPLAY=:N` alone can open on the live desktop. The private D-Bus session isolates the tray; `REAL_HOME` preserves Cargo and Rustup. The identifier suffix (`.labtest$DISPLAY_NUM`) avoids the live app AND other agents' concurrent labs — two labs on one identifier ping each other and the second exits. The seeded config disables cloud cleanup and typing; `method = "none"` still copies to a clipboard, but the private `XDG_RUNTIME_DIR` keeps `wl-copy` (default-socket lookup) and `ydotool` off the real session — clipboard writes land only on the lab display. Never test dictation paste-through against the real session.

Basic boot needs no real-profile files. To test dictation, opt in to model and whisper-cli access before launch:
```bash
source "$PWD/.lab.env"
WHISPER_CLI=$(command -v whisper-cli || echo "$REAL_HOME/.local/bin/whisper-cli")
[ -x "$WHISPER_CLI" ] || { echo "whisper-cli required" >&2; false; }
mkdir -p "$LAB_HOME/.local/bin" "$LAB_HOME/.local/share/whisper.cpp"
ln -s "$WHISPER_CLI" "$LAB_HOME/.local/bin/whisper-cli"
ln -s "$REAL_HOME/.local/share/whisper.cpp/models" "$LAB_HOME/.local/share/whisper.cpp/models"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve access to the real whisper-cli for lab dictation

This opt-in only links the real model directory into the lab home. In the common installed setup fixed by src/engine/mod.rs, whisper-cli may live in the real ~/.local/bin and not be on PATH; after the lab launch changes HOME to $LAB_HOME, find_command falls back to $LAB_HOME/.local/bin instead, so dictation tests still fail with “whisper-cli not found” even though the models were linked. Add the real ~/.local/bin to the lab PATH or link the CLI into $LAB_HOME/.local/bin along with the models.

Useful? React with 👍 / 👎.

```
Do not link real config, data, or credentials. Do not pass `--hidden`: the normal config starts the `main` window visible.

## Verify and screenshot

Wait for the Rust build, inspect the visible window, then capture the Xvfb root. ImageMagick `import` is available here:
```bash
source "$PWD/.lab.env"
import -display ":$DISPLAY_NUM" -window root "/tmp/pickscribe-lab-$DISPLAY_NUM.png"
find "$LAB_HOME" -maxdepth 5 -type f
```

## Cleanup

Find each PID first with `pgrep -f` (bracketed patterns avoid matching the search). Confirm the app PID has `$LAB_HOME` in its environment before killing it:
```bash
[ -f "$PWD/.lab.env" ] || { echo "no lab state in this checkout" >&2; false; }
source "$PWD/.lab.env"
: "${PORT:?}" "${DISPLAY_NUM:?}" "${LAB_HOME:?}"
pgrep -af "$PWD/target/debug/[p]ickscribe-app"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope cleanup to the lab app process

This pgrep matches every pickscribe-app binary built from the current workspace, not just the lab instance. If a normal dev instance is running from the same checkout at the same time (the lab identifier allows both to coexist), the output does not show DISPLAY or $LAB_HOME, so the following kill instruction can terminate the real dev app. Filter by the lab process environment/parent Tauri PID before presenting the PID to kill.

Useful? React with 👍 / 👎.

tr '\0' '\n' < /proc/<lab-app-pid>/environ | grep -qF "$LAB_HOME" || { echo "not the lab app" >&2; false; }
bash -lc 'kill <lab-app-pid>'
pgrep -af "$PWD/node_modules/.bin/[t]auri dev --config.*com.pickforge.pickscribe.labtest$DISPLAY_NUM"
bash -lc 'kill <tauri-cli-pid>'
pgrep -af "[v]ite.*--port $PORT"
bash -lc 'kill <vite-pid>'
pgrep -af "[x]fwm4 --display=:$DISPLAY_NUM"
bash -lc 'kill <xfwm4-pid>'
pgrep -af "[X]vfb :$DISPLAY_NUM"
bash -lc 'kill <xvfb-pid>'
rm -rf "$LAB_HOME"; rm -f "$PWD/.lab.env"
```
Each `bash -lc` call must do nothing except kill the inspected PID. Never use `pkill -f` in a compound command: its pattern can match the wrapper shell and terminate it instead (exit 144).
1 change: 1 addition & 0 deletions .claude/skills/run
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ src-tauri/gen/schemas/

# Updater signing keys — never commit; set as CI secrets instead.
src-tauri/pickscribe-updater.key*
.lab.env