Files
paperclip-harness/bin/screen

84 lines
3.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Open one screen as a TUIOS pane, or adopt the pane that already runs it.
#
# One screen, one workspace, named after the screen. The pane runs a shell that
# starts the program and returns to the shell when the program exits, so the
# pane never closes by itself; a workspace that loses its last pane is deleted
# by TUIOS and the rest renumber, which is the failure this avoids.
#
# bin/screen tree
# bin/screen reply AUD-162
#
# Run it twice and the second run focuses the existing pane. Print "created" or
# "adopted" so the caller can tell which happened.
set -euo pipefail
# The session is named, never taken from where the caller happens to be.
# TUIOS_SESSION is the session of the pane you are typing in, which is not
# necessarily this project's session; using it once put a screen in the wrong
# place.
SESSION="${PAPERCLIP_HARNESS_SESSION:-paperclip-harness}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BIN="$ROOT/bin/harness"
screen="${1:-}"
case "$screen" in
tree) args=(-screen tree -project "${2:-spike-2026-11-10}") ;;
reply) args=(-screen reply -issue "${2:?reply needs a task, for example AUD-162}") ;;
*) echo "usage: $(basename "$0") tree [project] | reply <task>" >&2; exit 2 ;;
esac
if [ ! -x "$BIN" ] || [ -n "$(find "$ROOT/tui" -name '*.go' -newer "$BIN" -print -quit 2>/dev/null)" ]; then
if ! command -v go >/dev/null 2>&1; then
echo "no harness binary and no Go to build one" >&2
exit 1
fi
( cd "$ROOT/tui" && go build -o "$BIN" . )
fi
# Workspaces and windows are resolved by name every time. A number is never
# remembered, because closing a pane renumbers the rest.
state="$(python3 - "$SESSION" "$screen" <<'PY'
import json, subprocess, sys
session, want = sys.argv[1], sys.argv[2]
def call(*args):
out = subprocess.run(["tuios", *args, "--json"], capture_output=True, text=True)
if out.returncode != 0:
sys.exit("tuios " + " ".join(args) + " failed: " + out.stderr.strip())
return json.loads(out.stdout)
ws = call("list-workspaces", "-s", session)["workspaces"]
win = call("list-windows", "-s", session)["windows"]
for w in win:
# custom_name is the name we gave the pane. It survives the program
# exiting and a shell taking over the title, so it is what adoption reads.
if w.get("custom_name") == want:
print("adopt", w["window_id"]); sys.exit()
named = next((w["workspace"] for w in ws if w.get("name") == want), None)
if named is not None:
print("create", named); sys.exit()
free = next((w["workspace"] for w in ws if w.get("window_count") == 0), None)
if free is None:
sys.exit("no empty workspace in session %r; close a pane or add a workspace" % session)
print("create", free)
PY
)"
action="$(echo "$state" | awk '{print $1}')"
where="$(echo "$state" | awk '{print $2}')"
if [ "$action" = "adopt" ]; then
tuios focus-window "$where" -s "$SESSION" >/dev/null
echo "adopted $screen ($where) in session $SESSION"
exit 0
fi
tuios new-window "$screen" -s "$SESSION" --workspace "$where" --cwd "$ROOT" \
--no-focus --keep-open -- /bin/sh -c "$BIN ${args[*]}; exec zsh -i" >/dev/null
tuios set-workspace-name "$where" "$screen" -s "$SESSION" >/dev/null
echo "created $screen on workspace $where of session $SESSION"