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
15 changes: 14 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,21 @@ jobs:

manifest = tomllib.loads(Path('herdr-plugin.toml').read_text())
assert manifest['id'] == 'termscope'
assert manifest['min_herdr_version'] >= '0.7.0'
assert manifest['version'] == '0.2.0'
assert manifest['min_herdr_version'] >= '0.7.4'
assert set(manifest['platforms']) == {'linux', 'macos'}
assert manifest['build'] == [{
'platforms': ['linux', 'macos'],
'command': ['sh', 'scripts/install-dependencies.sh'],
}]
assert {a['id'] for a in manifest['actions']} == {'open', 'open-links'}
assert {p['id'] for p in manifest['panes']} == {'picker', 'link-picker'}
assert all(p['placement'] == 'popup' for p in manifest['panes'])
assert all(p['width'] == '80%' and p['height'] == '60%' for p in manifest['panes'])

for path in Path('cable').glob('termscope-*.toml'):
channel = tomllib.loads(path.read_text())
assert len(channel['source']['command']) == 2
assert channel['source']['no_sort'] is True
assert channel['source']['frecency'] is False
PY
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## 0.2.0 - Unreleased

### Added

- Television channels with file previews, always-visible action hints, and `Ctrl-S` appearance/alphabetical source cycling.
- Built-in text and directory previews when `bat` is unavailable.
- Install-time Television provisioning through Homebrew for Herdr plugin installs.

### Changed

- Replaced fzf with Television `0.15+`.
- Replaced full-pane Herdr overlays with bounded `80% × 60%` session-modal popups.
- Raised the minimum Herdr version to `0.7.4`.

### Fixed

- Encoded picker targets before Television preview interpolation so filenames cannot inject shell syntax.
- Propagated Herdr popup and Television runtime failures while keeping user cancellation successful.
- Used the Plannotator slash command in agent panes and the CLI command in plain shells.
- Bounded preview reads by size, output, and wall-clock time.

## 0.1.0 - 2026-07-09

### Added
Expand Down
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

[![CI](https://github.com/iurysza/termscope/actions/workflows/ci.yml/badge.svg)](https://github.com/iurysza/termscope/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![herdr 0.7+](https://img.shields.io/badge/herdr-0.7%2B-8a2be2)
![herdr 0.7.4+](https://img.shields.io/badge/herdr-0.7.4%2B-8a2be2)
![platforms: macOS • Linux](https://img.shields.io/badge/platforms-macOS%20%E2%80%A2%20Linux-informational)

Open the files and links your agent just mentioned.
Expand Down Expand Up @@ -39,14 +39,16 @@ Termscope stays conservative:
- verifies paths against the repo/worktree on disk
- preserves `file:line` targets
- falls back to a full repo picker when no visible file matches
- opens outside the picker overlay, so the agent conversation stays where it is
- uses a session-modal Herdr popup, leaving the tiled pane layout untouched

## Requirements

- [Herdr](https://herdr.dev) `>= 0.7.0` or tmux
- [Herdr](https://herdr.dev) `>= 0.7.4` or tmux
- Python `>= 3.10`
- [`fd`](https://github.com/sharkdp/fd)
- [`fzf`](https://github.com/junegunn/fzf)
- [Homebrew](https://brew.sh) when a Herdr plugin install needs to add or upgrade Television
- [Television](https://alexpasmantier.github.io/television/) `>= 0.15` (provisioned automatically when needed)
- [`bat`](https://github.com/sharkdp/bat) is optional for syntax-highlighted previews
- `nvim` for the default file-open action
- `open` on macOS or `xdg-open` on Linux for default-app opens

Expand All @@ -59,11 +61,18 @@ environment provides it.
herdr plugin install iurysza/termscope
```

For local development:
The install includes a visible build step that installs or upgrades Television
through Homebrew when `tv` is missing or older than `0.15`. It never installs
Homebrew itself; a missing Homebrew installation aborts cleanly before the
plugin is registered.

For local development (`plugin link` does not run install-time build steps):

```bash
git clone https://github.com/iurysza/termscope.git
herdr plugin link ./termscope
cd termscope
./scripts/install-dependencies.sh
herdr plugin link "$PWD"
```

Verify Herdr sees the actions:
Expand Down Expand Up @@ -110,7 +119,7 @@ File picker controls:
| --- | --- |
| `Enter` | Open in a new Neovim split beside the source pane |
| `Ctrl-O` | Open with the default app |
| `Ctrl-Y` | Send `/plannotator-annotate <file>` to the source pane |
| `Ctrl-Y` | Agent pane: send `/plannotator-annotate <file>`; shell pane: run `plannotator annotate <file>` |
| `Ctrl-S` | Toggle appearance order / alphabetical sort |

Link picker controls:
Expand Down Expand Up @@ -160,7 +169,7 @@ export TERMSCOPE_SORT=alpha

## Dry run / debug

See what the scanner would offer without opening `fzf`:
See what the scanner would offer without opening Television:

```bash
./termscope scan --pane-path "$PWD" --pane-id "$HERDR_PANE_ID" --multiplexer herdr
Expand All @@ -177,10 +186,12 @@ selection decision.

## How it works

Herdr plugin actions run without a TTY, so `termscope.open` does not run `fzf`
directly. It opens a Herdr-managed overlay pane. That pane inherits the source
pane id/cwd, captures visible text with `herdr pane read --source visible`, scans
the repo with `fd`, and runs the interactive picker.
Herdr plugin actions run without a TTY, so `termscope.open` first opens an
`80% × 60%` session-modal popup. The popup inherits the source pane id/cwd,
captures visible text with `herdr pane read --source visible`, scans the repo
with `fd`, and runs Television. Two bundled channels let `Ctrl-S` cycle between
appearance and alphabetical order. File previews use `bat` when available and a
built-in text preview otherwise.

When you choose a file, Termscope asks Herdr to split beside the source pane and
runs `nvim +line path`. For URLs, it uses the default opener unless
Expand Down
31 changes: 31 additions & 0 deletions cable/termscope-alpha.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[metadata]
name = "termscope-alpha"
description = "Pick visible terminal targets in alphabetical order"

[source]
command = [
{ name = "Alphabetical", run = "cat \"$TERMSCOPE_ALPHA_CANDIDATES\"" },
{ name = "Appearance", run = "cat \"$TERMSCOPE_APPEARANCE_CANDIDATES\"" },
]
display = "{split:\\t:1..}"
output = "{split:\\t:0}"
no_sort = true
frecency = false

[keybindings]
ctrl-s = "cycle_sources"

[preview]
command = "\"$TERMSCOPE_PYTHON\" \"$TERMSCOPE_SCRIPT\" preview --pane-path \"$TERMSCOPE_PANE_PATH\" {split:\\t:0}"

[ui]
layout = "landscape"
input_prompt = "> "

[ui.preview_panel]
size = 60
footer = "Enter Neovim · Ctrl-O Default app · Ctrl-Y Plannotator"
border_type = "rounded"

[ui.results_panel]
border_type = "rounded"
31 changes: 31 additions & 0 deletions cable/termscope-appearance.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[metadata]
name = "termscope-appearance"
description = "Pick visible terminal targets in appearance order"

[source]
command = [
{ name = "Appearance", run = "cat \"$TERMSCOPE_APPEARANCE_CANDIDATES\"" },
{ name = "Alphabetical", run = "cat \"$TERMSCOPE_ALPHA_CANDIDATES\"" },
]
display = "{split:\\t:1..}"
output = "{split:\\t:0}"
no_sort = true
frecency = false

[keybindings]
ctrl-s = "cycle_sources"

[preview]
command = "\"$TERMSCOPE_PYTHON\" \"$TERMSCOPE_SCRIPT\" preview --pane-path \"$TERMSCOPE_PANE_PATH\" {split:\\t:0}"

[ui]
layout = "landscape"
input_prompt = "> "

[ui.preview_panel]
size = 60
footer = "Enter Neovim · Ctrl-O Default app · Ctrl-Y Plannotator"
border_type = "rounded"

[ui.results_panel]
border_type = "rounded"
12 changes: 9 additions & 3 deletions docs/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ gh repo edit iurysza/termscope \
gh repo edit iurysza/termscope \
--add-topic herdr-plugin \
--add-topic herdr \
--add-topic fzf \
--add-topic television \
--add-topic tmux \
--add-topic terminal \
--add-topic python
Expand All @@ -39,20 +39,26 @@ marketplace index.
```bash
git add .
git commit -m "chore: prepare Termscope release"
git tag v0.1.0
git tag v0.2.0
git push origin main --tags
gh release create v0.1.0 --title "v0.1.0" --notes-file CHANGELOG.md
gh release create v0.2.0 --title "v0.2.0" --notes-file CHANGELOG.md
```

## Install smoke test

On a clean machine or temp user profile:

Start with Television absent or older than `0.15`, but with Homebrew available:

```bash
herdr --version # 0.7.4+
herdr plugin install iurysza/termscope
tv --version # now 0.15+
herdr plugin action list --plugin termscope
```

Also verify installation aborts before registration when Homebrew is absent.

Then add keybindings from the README and run:

```bash
Expand Down
24 changes: 16 additions & 8 deletions herdr-plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,46 @@
# herdr plugin link /path/to/termscope
#
# Termscope opens real files and URLs already visible in the active pane. The
# user-facing action opens a Herdr overlay pane because actions run without a
# TTY and fzf needs one.
# action opens a bounded Herdr popup because plugin actions run without a TTY
# and Television needs an interactive terminal.

id = "termscope"
name = "Termscope"
version = "0.1.0"
min_herdr_version = "0.7.0"
version = "0.2.0"
min_herdr_version = "0.7.4"
description = "Open files and links already visible on your terminal screen."
platforms = ["linux", "macos"]

[[build]]
platforms = ["linux", "macos"]
command = ["sh", "scripts/install-dependencies.sh"]

[[actions]]
id = "open"
title = "Termscope: open visible file picker"
description = "Scan the focused pane for visible file paths and open a picker overlay."
description = "Scan the focused pane for visible file paths and open a Television popup."
contexts = ["workspace"]
command = ["python3", "termscope_herdr.py", "--open-pane"]

[[actions]]
id = "open-links"
title = "Termscope: open visible link picker"
description = "Scan the focused pane for visible URLs and open a picker overlay."
description = "Scan the focused pane for visible URLs and open a Television popup."
contexts = ["workspace"]
command = ["python3", "termscope_herdr.py", "--open-links-pane"]

[[panes]]
id = "picker"
title = "Termscope"
placement = "overlay"
placement = "popup"
width = "80%"
height = "60%"
command = ["python3", "termscope_herdr.py"]

[[panes]]
id = "link-picker"
title = "Termscope Links"
placement = "overlay"
placement = "popup"
width = "80%"
height = "60%"
command = ["python3", "termscope_herdr.py", "--links"]
56 changes: 56 additions & 0 deletions scripts/install-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/bin/sh
set -eu

television_supported() {
candidate="${1:-}"
[ -n "$candidate" ] || return 1

output="$("$candidate" --version 2>/dev/null)" || return 1
case "$output" in
"television "*) version=${output#television } ;;
*) return 1 ;;
esac
version=${version%% *}

old_ifs=$IFS
IFS=.
set -- $version
IFS=$old_ifs
major=${1:-}
minor=${2:-}
case "$major" in ''|*[!0-9]*) return 1 ;; esac
case "$minor" in ''|*[!0-9]*) return 1 ;; esac

[ "$major" -gt 0 ] || { [ "$major" -eq 0 ] && [ "$minor" -ge 15 ]; }
}

tv_bin="$(command -v tv 2>/dev/null || true)"
if television_supported "$tv_bin"; then
echo "Television 0.15+ already installed: $tv_bin"
exit 0
fi

brew_bin="$(command -v brew 2>/dev/null || true)"
if [ -z "$brew_bin" ]; then
echo "Termscope requires Homebrew to install Television 0.15+." >&2
echo "Install Homebrew from https://brew.sh, then retry the plugin install." >&2
exit 1
fi

if "$brew_bin" list --formula television >/dev/null 2>&1; then
echo "Upgrading Television with Homebrew"
"$brew_bin" upgrade television
else
echo "Installing Television with Homebrew"
"$brew_bin" install television
fi

hash -r 2>/dev/null || true
tv_bin="$(command -v tv 2>/dev/null || true)"
if ! television_supported "$tv_bin"; then
echo "Homebrew completed, but Television 0.15+ is not available as 'tv' on PATH." >&2
[ -n "$tv_bin" ] && echo "Current tv: $tv_bin" >&2
exit 1
fi

echo "Television installed: $tv_bin"
Loading
Loading