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
44 changes: 44 additions & 0 deletions .github/workflows/runtime-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Runtime tests

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
test:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest

steps:
- name: Check out repository
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Test runtime installer
run: bash tests/install-runtime-test.sh

- name: Test runtime integration
run: bash tests/runtime-integration-test.sh

- name: Check script syntax
shell: bash
run: |
for file in scripts/*.sh tests/*.sh; do
bash -n "$file"
done
for file in scripts/install-runtime.sh scripts/runtime-versions.sh; do
/bin/sh -n "$file"
done
python3 -m compileall -q scripts
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/bin/
scripts/__pycache__/
demo/out*/
.DS_Store
28 changes: 27 additions & 1 deletion DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,33 @@ Two supporting conventions:
- The settings interface always displays the message "Changes apply automatically when you return to the lazygit pane (hot reload)" to set user expectations.
- `run-lazygit.sh` also runs the generator (idempotently) immediately before `exec`, ensuring cold starts and hot reloads see the same generated layer. There is one generation path and no competing source of truth.

## 6. Strategic Boundaries and Stop Signals
## 6. Reproducible Runtime Packaging

The plugin treats lazygit and fzf as part of its tested runtime, not as mutable
system dependencies. A GitHub install runs `scripts/install-runtime.sh`, which
maps macOS/Linux and x86_64/ARM64 to pinned upstream release archives, verifies
repository-pinned SHA-256 digests, and writes both executables to the managed
plugin checkout's `bin/` directory. Runtime scripts resolve those files by
absolute path through `runtime-env.sh`; they never invoke a same-named binary
from the user's `PATH`.

This is deliberately different from package-manager bootstrapping:

- installation never invokes Homebrew, apt, dnf, pacman, or `sudo`;
- the same plugin version uses the same lazygit/fzf versions everywhere;
- key-conflict analysis and generated configuration target the binary that
actually runs;
- reinstalling the plugin atomically replaces its managed checkout and runtime;
- `plugin link` remains a development operation and requires running
`scripts/install-runtime.sh` manually because Herdr does not execute build
commands for linked plugins.

The remaining host requirements are Herdr, Bash, Git, Python >= 3.7, standard
archive/hash utilities, and either curl or wget. Python is an explicit runtime
requirement because pane geometry, JSON handling, locking, timeout handling,
and key analysis use its standard library.

## 7. Strategic Boundaries and Stop Signals

**Out of scope** (unreachable through the lazygit approach and therefore fake integration):

Expand Down
25 changes: 15 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,34 @@ One of these CLIs installed and logged in: `claude`, `codex`, `opencode`, `gemin

## Install

Requires herdr >= 0.7.0. `lazygit` and `fzf` are checked at install time and installed via Homebrew if missing.
Requires herdr >= 0.7.0 plus `bash`, `git`, and Python >= 3.7 (`python3`) on `PATH`. During a GitHub install, the plugin downloads pinned private copies of lazygit 0.63.0 and fzf 0.74.0, verifies repository-pinned SHA-256 digests, and stores them under its managed `bin/` directory. It never invokes Homebrew, a system package manager, or `sudo`. The build also needs `curl` or `wget`, `tar`, and `sha256sum` or `shasum`.

```sh
herdr plugin install crokily/herdr-lazygit

# Or link a local checkout during development
herdr plugin link /path/to/herdr-lazygit
# Local development: plugin link does not run [[build]], so prepare the runtime first.
cd /path/to/herdr-lazygit
/bin/sh scripts/install-runtime.sh
herdr plugin link "$PWD"
```

Then add keybindings to your herdr `config.toml`:

```toml
[[keys.command]] # lazygit: open in a split
key = "prefix+g"
type = "shell"
command = "herdr plugin action invoke open --plugin herdr-lazygit"
type = "plugin_action"
command = "herdr-lazygit.open"

[[keys.command]] # lazygit: open in its own tab
key = "prefix+shift+g"
type = "shell"
command = "herdr plugin action invoke open-tab --plugin herdr-lazygit"
type = "plugin_action"
command = "herdr-lazygit.open-tab"
```

Run `herdr server reload-config`. `prefix+g` then behaves as: not open → open in a split; open but unfocused → focus; focused → close.

> **Note (herdr platform behavior):** an action's context always resolves from the pane that currently has **UI focus**, not from the process that invoked it. Invoking `herdr plugin action invoke …` from a background pane or script opens lazygit next to the user's focused pane, takes its cwd from that pane, and steals focus. Trigger these actions only through foreground keybindings.
> **Note (herdr platform behavior):** an action's context always resolves from the pane that currently has **UI focus**, not from a background process. The action opens lazygit next to the user's focused pane, takes its cwd from that pane, and focuses the new pane. Trigger these actions only through foreground keybindings.

## Reference

Expand Down Expand Up @@ -124,9 +126,12 @@ To remap a plugin key, use the settings pane (stored in `keys.conf`). To remap a
herdr-plugin.toml # plugin manifest
lazygit-config.yml # bundled base config (factory layer)
DESIGN.md # design doc: three-verb model, key rules, config layers
THIRD_PARTY_NOTICES.md # licenses for downloaded lazygit/fzf binaries
bin/ # generated private lazygit + fzf runtime (not committed)
scripts/
ensure-lazygit.sh # install-time: detect/install lazygit
ensure-fzf.sh # install-time: detect/install fzf (settings + commit UI)
install-runtime.sh # install-time: download + verify the private runtime
runtime-versions.sh # pinned lazygit/fzf versions
runtime-env.sh # resolve runtime tools by absolute path
run-lazygit.sh # pane entrypoint: regenerate config layer, exec lazygit
open-lazygit.sh # action: open in a split (idempotent open/focus/toggle)
open-lazygit-tab.sh # action: open in a tab
Expand Down
25 changes: 15 additions & 10 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,32 +56,34 @@

## 安装

要求 herdr >= 0.7.0。`lazygit` 和 `fzf` 在插件安装时自动检测,缺失时通过 Homebrew 安装。
要求 herdr >= 0.7.0,并确保 `bash`、`git`、Python >= 3.7(`python3`)在 `PATH` 上。通过 GitHub 安装时,插件会下载固定版本的私有 lazygit 0.63.0 与 fzf 0.74.0,用仓库内固定的 SHA-256 校验值验证后存入 Herdr 管理的插件 `bin/` 目录。安装过程不会调用 Homebrew、系统包管理器或 `sudo`。构建阶段还需要 `curl` 或 `wget`、`tar`,以及 `sha256sum` 或 `shasum`。

```sh
herdr plugin install crokily/herdr-lazygit

# 或本地开发时软链
herdr plugin link /path/to/herdr-lazygit
# 本地开发:plugin link 不执行 [[build]],所以先准备私有 runtime
cd /path/to/herdr-lazygit
/bin/sh scripts/install-runtime.sh
herdr plugin link "$PWD"
```

然后在 herdr 的 `config.toml` 里添加快捷键:

```toml
[[keys.command]] # lazygit:分屏打开
key = "prefix+g"
type = "shell"
command = "herdr plugin action invoke open --plugin herdr-lazygit"
type = "plugin_action"
command = "herdr-lazygit.open"

[[keys.command]] # lazygit:独立 tab 打开
key = "prefix+shift+g"
type = "shell"
command = "herdr plugin action invoke open-tab --plugin herdr-lazygit"
type = "plugin_action"
command = "herdr-lazygit.open-tab"
```

执行 `herdr server reload-config` 后生效。`prefix+g` 的行为:未打开 → 分屏打开;已打开但未聚焦 → 聚焦;已聚焦 → 关闭。

> **注意(herdr 平台行为)**:action 的上下文永远取自 herdr 当前 **UI 聚焦**的 pane,而不是发起调用的进程。从后台 pane 或脚本执行 `herdr plugin action invoke …` 会把 lazygit 开在用户聚焦 pane 旁边、使用那个 pane 的 cwd,并夺走焦点。只通过前台键位绑定触发这两个 action。
> **注意(herdr 平台行为)**:action 的上下文永远取自 herdr 当前 **UI 聚焦**的 pane,而不是后台进程。它会把 lazygit 开在用户聚焦 pane 旁边、使用该 pane 的 cwd,并聚焦新 pane。只通过前台键位绑定触发这两个 action。

## 细节备查

Expand Down Expand Up @@ -124,9 +126,12 @@ AI_CUSTOM_CMD=""
herdr-plugin.toml # 插件 manifest
lazygit-config.yml # 捆绑的基础配置(出厂层)
DESIGN.md # 设计文档:三动词模型、选键规则、三层配置
THIRD_PARTY_NOTICES.md # 下载的 lazygit/fzf 二进制许可证
bin/ # 构建生成的私有 lazygit + fzf runtime(不提交)
scripts/
ensure-lazygit.sh # 安装时检测/安装 lazygit
ensure-fzf.sh # 安装时检测/安装 fzf(设置页 + commit UI)
install-runtime.sh # 安装时下载并校验私有 runtime
runtime-versions.sh # 固定的 lazygit/fzf 版本
runtime-env.sh # 用绝对路径解析 runtime 工具
run-lazygit.sh # pane 入口:重新生成配置层后 exec lazygit
open-lazygit.sh # action:分屏打开(幂等 open/focus/toggle)
open-lazygit-tab.sh # action:tab 打开
Expand Down
57 changes: 57 additions & 0 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Third-party notices

`herdr-lazygit` downloads the following pinned upstream binaries into its
Herdr-managed `bin/` directory during plugin installation. The binaries are not
committed to this repository.

## lazygit 0.63.0

Source: <https://github.com/jesseduffield/lazygit/tree/v0.63.0>

MIT License

Copyright (c) 2018 Jesse Duffield

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

## fzf 0.74.0

Source: <https://github.com/junegunn/fzf/tree/v0.74.0>

The MIT License (MIT)

Copyright (c) 2013-2026 Junegunn Choi

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
14 changes: 5 additions & 9 deletions herdr-plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@

id = "herdr-lazygit"
name = "herdr-lazygit"
version = "0.1.0"
version = "0.1.1"
description = "lazygit in a herdr split pane, with AI-assisted commit messages via custom commands."
min_herdr_version = "0.7.0"
platforms = ["linux", "macos"]

# Install-time build step: make sure the `lazygit` binary is available
# (install via Homebrew when possible, otherwise print instructions and fail).
# Install the pinned, plugin-private lazygit + fzf runtime for this platform.
# The build downloads official release archives, verifies repository-pinned
# SHA-256 digests, and writes only under this managed checkout's bin/ directory.
[[build]]
command = ["/bin/sh", "scripts/ensure-lazygit.sh"]

# Install-time build step: make sure `fzf` is available (the settings and AI
# commit pane menus are built on it).
[[build]]
command = ["/bin/sh", "scripts/ensure-fzf.sh"]
command = ["/bin/sh", "scripts/install-runtime.sh"]

# The lazygit pane. The [[actions]] below summon it; run-lazygit.sh assembles the
# layered LG_CONFIG_FILE (bundled config + user override) and then execs lazygit.
Expand Down
16 changes: 6 additions & 10 deletions scripts/ai-commit-pane.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
# shellcheck disable=SC1091
. "$script_dir/runtime-env.sh"
FZF_BIN="$HERDR_LAZYGIT_FZF_BIN"
AI_SH="$script_dir/ai-commit-msg.sh"
repo="${1:-$(pwd)}"
config_dir="${HERDR_PLUGIN_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/herdr-lazygit}"
Expand Down Expand Up @@ -64,15 +67,8 @@ fi
clear
printf 'herdr-lazygit · AI Commit\nBackend: %s · Model: %s\n\n' "$backend_text" "$model"

if ! command -v fzf >/dev/null 2>&1; then
cat >&2 <<'EOF'
fzf is required for the AI commit candidate and message editor.

Install it with:
brew install fzf

Press any key to close...
EOF
if ! herdr_lazygit_require_runtime fzf; then
printf '\nPress any key to close...' >&2
IFS= read -rsn1 _ || true
exit 1
fi
Expand Down Expand Up @@ -171,7 +167,7 @@ preview_cmd='msg={}; if [ -n "$msg" ]; then printf "\033[1m✏ %s\033[0m\n\n" "$

fzf_out=""
fzf_rc=0
fzf_out="$(fzf --layout=reverse --no-multi --print-query \
fzf_out="$("$FZF_BIN" --layout=reverse --no-multi --print-query \
--prompt='Commit message > ' \
--header='Enter=commit · type to edit/write · Esc=cancel' \
--wrap --gap 1 --gap-line --highlight-line \
Expand Down
35 changes: 0 additions & 35 deletions scripts/ensure-fzf.sh

This file was deleted.

34 changes: 0 additions & 34 deletions scripts/ensure-lazygit.sh

This file was deleted.

Loading