@n-seiji/nuthatch 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +59 -183
- package/README.md +59 -189
- package/dist/{clean-rn2476my.js → clean-6q90wtve.js} +1 -1
- package/dist/{cli-hz0r7zw8.js → cli-aj2y82fq.js} +1 -1
- package/dist/{cli-sre95k9x.js → cli-mx5n3y1f.js} +1 -1
- package/dist/{cli-pick-yjd4qpd9.js → cli-pick-8qtma4v0.js} +3 -3
- package/dist/{cli-update-x0r4b4xk.js → cli-update-rjqr81xx.js} +1 -1
- package/dist/{cli-9n04gsq1.js → cli-vt5mddc4.js} +1 -1
- package/dist/cli.js +8 -8
- package/dist/{jump-1zkraat1.js → jump-vrwxwb1f.js} +1 -1
- package/dist/{ls-3hyjf60d.js → ls-hvwtaxv8.js} +1 -1
- package/dist/{rm-eg3j8ae1.js → rm-a5d1c4mh.js} +1 -1
- package/dist/{root-49vcb8x2.js → root-zbrsb9md.js} +1 -1
- package/package.json +1 -1
- package/dist/{cli-y8vsqre6.js → cli-4f1vz23g.js} +3 -3
package/README.ja.md
CHANGED
|
@@ -1,198 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
hop ls [--json] # worktree 一覧 (dirty, ahead/behind, kind)
|
|
32
|
-
hop rm <branch> # worktree を削除 (branch は残す) — managed/external 問わず、dirty には --force が必要
|
|
33
|
-
hop clean # ゴミ worktree を自動判定して削除 (managed のみ)
|
|
34
|
-
hop root <branch> # root clone を一時的に切替 (動作確認用) —
|
|
35
|
-
# 対象 branch が他所で checkout 済みなら、clean かつ unlocked な holder を swap する
|
|
36
|
-
hop root - # root clone を元に戻す (root の branch のみ; swap で detach した holder はそのまま)
|
|
37
|
-
|
|
38
|
-
hop -- <branch> # 予約コマンドと被る branch 名をエスケープ
|
|
39
|
-
# (`hop` 自体と同名の branch (例: `hop -- hop`) にも必要)
|
|
40
|
-
hop --help # usage を表示 (-h / hop help でも同じ)
|
|
41
|
-
|
|
42
|
-
hop --version # hop の version を表示
|
|
43
|
-
hop --update # standalone バイナリを最新リリースへ置換 (それ以外は更新方法を表示)
|
|
44
|
-
hop --update --check # 更新があるかを報告するだけ — 何も変更しない
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
同じ branch が複数の worktree で checkout されている場合、branch だけを指定する
|
|
48
|
-
`hop rm` は削除対象を推測せず拒否する。対話的 picker は選択した path を、lock 内の
|
|
49
|
-
最終安全確認まで保持する。
|
|
50
|
-
|
|
51
|
-
### 対話的 picker
|
|
52
|
-
|
|
53
|
-
picker は候補を section に分けて表示する — 既存の worktree (root が先頭) と
|
|
54
|
-
未作成の branch — ステータスマーカー・整列した列・短縮パス付きで:
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
WORKTREES
|
|
58
|
-
❯ ● main root ~/ghq/.../nuthatch
|
|
59
|
-
○ feat/picker managed …/_worktree/feat__picker
|
|
60
|
-
● codex/fix-x ext …/.claude/worktrees/x
|
|
61
|
-
|
|
62
|
-
BRANCHES — Enter creates a worktree
|
|
63
|
-
+ feat/idea local
|
|
64
|
-
+ origin/hotfix remote
|
|
65
|
-
|
|
66
|
-
(●=dirty ○=clean +=not created)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
candidate が 0 件になった section は (header ごと) 丸ごと消える —
|
|
70
|
-
検索クエリで絞り込んだ結果 0 件になった場合も同様。
|
|
71
|
-
|
|
72
|
-
各 section 内の順序は insertion 順ではなく固定: WORKTREES は root が先頭、
|
|
73
|
-
次に managed、その次に external (各グループ内は branch 名昇順、detached
|
|
74
|
-
HEAD の worktree はグループ内最後); BRANCHES は remote より local が先
|
|
75
|
-
(各グループ内は branch 名昇順)。これは検索フィルタ中も保たれる —
|
|
76
|
-
絞り込んでも残った項目の並びは変わらない。BRANCHES の行は dim 表示になり、
|
|
77
|
-
○/●/+ マーカーに加えて「既に worktree」か「まだ未作成」かが一目でわかる。
|
|
78
|
-
|
|
79
|
-
picker は terminal の alternate screen buffer (fzf や vim と同じ仕組み) で
|
|
80
|
-
動くため、scrollback 履歴に残らない — 実行のたびに画面を上書きし、
|
|
81
|
-
どう終了しても (選択、Esc/Ctrl-C、割り込み) shell の画面をきれいに復元する。
|
|
82
|
-
|
|
83
|
-
### 対話的 picker のキー
|
|
84
|
-
|
|
85
|
-
action panel は候補一覧の隣に列として表示される (overlay ではない) ので、
|
|
86
|
-
action を選ぶ間も一覧が見え続ける。横に並べられない幅 (おおよそ 94 列
|
|
87
|
-
未満) の terminal では、一覧の下にスタックする形にフォールバックする。
|
|
88
|
-
|
|
89
|
-
| キー | 動作 |
|
|
90
|
-
|---|---|
|
|
91
|
-
| `Enter` | 選択中の候補へ cd |
|
|
92
|
-
| `Tab`, `→`, `Ctrl+L`, `Ctrl+F` | 選択中の候補の action panel を開く (ここで cd / 削除 / root 切替) |
|
|
93
|
-
| `Ctrl+X` | 選択中の worktree を削除 (常に先に y/N を確認) |
|
|
94
|
-
| `Ctrl+R` | root clone を選択中の branch へ切替 (`external` は先に y/N を確認) |
|
|
95
|
-
| `↑`/`↓`, `Ctrl+P`/`Ctrl+N`, `Ctrl+K` | 選択を移動 (矢印・emacs・vim キーがすべて併用可) |
|
|
96
|
-
| `Esc` | 静かにキャンセル — exit 0、stdout は空のまま (shell wrapper は cd しない) |
|
|
97
|
-
| `Ctrl+C` | 割り込みとしてキャンセル — exit 130、実際の SIGINT と同じ |
|
|
98
|
-
|
|
99
|
-
action panel 内: 同じ上下移動キー (左右は閉じる操作に予約されており、
|
|
100
|
-
移動としては使われない)、`Enter` でハイライト中の action を実行、
|
|
101
|
-
`c`/`d`/`r` で cd/delete/switch-root を直接実行、`Esc`、`Tab`、`←`、
|
|
102
|
-
`Ctrl+H` で一覧に戻る (`←`/`Ctrl+H` は開く操作の `→`/`Ctrl+L`/`Ctrl+F` と
|
|
103
|
-
対になっている; `Tab` はどちらの状態からでもトグルする — Ghostty のように
|
|
104
|
-
Cmd+K のようなキーの組み合わせを Tab に remap する terminal で便利)。
|
|
105
|
-
`delete` はすでに作成済みの worktree (`managed` / `external`) すべてに
|
|
106
|
-
表示される (panel からの削除は、`external` worktree と、git が作業ツリーを
|
|
107
|
-
見失った prunable な worktree なら先に y/N を確認する。Ctrl+X は常に
|
|
108
|
-
確認する); `switch root here` は root worktree 自身には表示されず、
|
|
109
|
-
`external` worktree に対しては HEAD を detach する可能性があるため
|
|
110
|
-
同じく先に y/N を確認する。削除すると候補一覧が再読み込みされ、picker を
|
|
111
|
-
抜けずに削除を続けられる; cd と switch-root は終了して結果の path を
|
|
112
|
-
出力する (hop の stdout 契約どおり)。
|
|
113
|
-
|
|
114
|
-
Shell 統合 (自動 `cd`):
|
|
115
|
-
|
|
116
|
-
```sh
|
|
117
|
-
# ~/.zshrc
|
|
118
|
-
eval "$(hop init zsh)"
|
|
119
|
-
```
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/logo-dark.svg">
|
|
3
|
+
<img src="docs/assets/logo-light.svg" alt="nuthatch" width="360">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
[English](README.md) · **日本語**
|
|
7
|
+
|
|
8
|
+
**git worktree を、作る・移る・消す。ぜんぶ `hop` ひとつで。**
|
|
9
|
+
|
|
10
|
+
`nuthatch` は、人間と AI coding agent のための zero-setup な git worktree manager。コマンドは `hop` ひとつで、設定も状態ファイルもいらない。[Web サイト](https://www.sijis.me/projects/nuthatch)
|
|
11
|
+
|
|
12
|
+
<table>
|
|
13
|
+
<tr>
|
|
14
|
+
<td align="center" valign="top" width="33%">
|
|
15
|
+
<img src="docs/assets/tile-create.svg" width="200" alt="worktree を作る"><br>
|
|
16
|
+
<b>作る</b><br>
|
|
17
|
+
<sub>branch も worktree も、その場で。</sub>
|
|
18
|
+
</td>
|
|
19
|
+
<td align="center" valign="top" width="33%">
|
|
20
|
+
<img src="docs/assets/tile-jump.svg" width="200" alt="worktree へ移る"><br>
|
|
21
|
+
<b>移る</b><br>
|
|
22
|
+
<sub><code>hop</code> で選び、<code>hop -</code> で戻る。</sub>
|
|
23
|
+
</td>
|
|
24
|
+
<td align="center" valign="top" width="33%">
|
|
25
|
+
<img src="docs/assets/tile-remove.svg" width="200" alt="worktree を消す"><br>
|
|
26
|
+
<b>消す</b><br>
|
|
27
|
+
<sub>1 つずつ、または merge 済みをまとめて。</sub>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
</table>
|
|
120
31
|
|
|
121
32
|
## Install
|
|
122
33
|
|
|
123
|
-
> まだリリースされていない — バージョンは publish も tag もされていない。
|
|
124
|
-
> 最初の `v*` tag が出た後は、以下の選択肢が記載どおりに動く。
|
|
125
|
-
|
|
126
|
-
推奨: GitHub Release のバイナリ (bun 製、mise なら
|
|
127
|
-
`mise use github:n-seiji/nuthatch`)。npm 版は Node で動くため git 呼び出しが遅い
|
|
128
|
-
(hop ls ~400ms vs ~60ms) — CI や頻繁な呼び出しにはバイナリを使う。
|
|
129
|
-
|
|
130
34
|
```sh
|
|
131
|
-
# ビルド済みバイナリ (macOS arm64/x64, Linux x64) — Node.js 不要、最速:
|
|
132
35
|
curl -fsSL https://raw.githubusercontent.com/n-seiji/nuthatch/main/install.sh | sh
|
|
133
|
-
mise use github:n-seiji/nuthatch # mise、GitHub Release のバイナリ経由
|
|
134
|
-
|
|
135
|
-
# npm (低速: Node 経由で git を呼ぶ — バイナリの hop ls ~60ms に対し ~400ms)
|
|
136
|
-
npm i -g @n-seiji/nuthatch # または: bunx @n-seiji/nuthatch
|
|
137
|
-
mise use -g npm:@n-seiji/nuthatch # mise、npm 経由
|
|
138
36
|
```
|
|
139
37
|
|
|
140
|
-
|
|
141
|
-
上書き可)、常に最新の GitHub Release を取得する; 特定バージョンに固定する
|
|
142
|
-
には `HOP_VERSION=vX.Y.Z` を使う。Linux arm64 はビルド済みバイナリが
|
|
143
|
-
まだないため、npm install を使う。
|
|
144
|
-
|
|
145
|
-
v0.2.0 より後のリリースのバイナリにはビルド来歴 (build provenance) の attestation
|
|
146
|
-
が付く。ダウンロードしたバイナリを手動で検証するには:
|
|
38
|
+
続けて `~/.zshrc` に次を足すと、`hop` が `cd` までしてくれる。
|
|
147
39
|
|
|
148
40
|
```sh
|
|
149
|
-
|
|
150
|
-
--signer-workflow github.com/n-seiji/nuthatch/.github/workflows/release.yml
|
|
41
|
+
eval "$(hop init zsh)"
|
|
151
42
|
```
|
|
152
43
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
### 更新
|
|
156
|
-
|
|
157
|
-
```sh
|
|
158
|
-
hop --update # standalone バイナリを最新リリースへ置換
|
|
159
|
-
hop --update --check # 報告だけ: current / latest / インストール方法 / すべきこと
|
|
160
|
-
hop --version # 今どの version か
|
|
161
|
-
```
|
|
44
|
+
npm、mise、Linux arm64、バイナリの検証、更新については [ガイド](docs/guide.ja.md#install) を参照。
|
|
162
45
|
|
|
163
|
-
|
|
164
|
-
ディレクトリに書き込めて (バイナリと checksum のダウンロードの前に確認する)、
|
|
165
|
-
かつリリースに添付された `.sha256` と SHA-256 が一致した場合に限る — 不一致なら
|
|
166
|
-
何も置換しない (確認するのは SHA-256 のみで、attestation は見ない)。それ以外のインストールでは、何も実行せず、リクエストも送らず、
|
|
167
|
-
更新方法を 1 行表示して終了する (exit 1):
|
|
46
|
+
## 使い方
|
|
168
47
|
|
|
169
|
-
|
|
|
48
|
+
| コマンド | 動作 |
|
|
170
49
|
|---|---|
|
|
171
|
-
| `
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
50
|
+
| `hop` | worktree/branch を対話的に選んで `cd` |
|
|
51
|
+
| `hop <branch>` | その branch の worktree へ `cd` |
|
|
52
|
+
| `hop <branch> --create` | 未作成ならその場で worktree を作成 |
|
|
53
|
+
| `hop -` | 直前の worktree へ戻る |
|
|
54
|
+
| `hop root` | root clone へ `cd` |
|
|
55
|
+
| `hop ls [--json]` | worktree 一覧 (dirty, ahead/behind, kind) |
|
|
56
|
+
| `hop rm <branch>` | worktree を削除 (branch は残す) |
|
|
57
|
+
| `hop clean` | ゴミ worktree を自動判定して削除 (既定では managed のみ) |
|
|
58
|
+
| `hop --update` | standalone バイナリを最新リリースへ置換 |
|
|
175
59
|
|
|
176
|
-
|
|
177
|
-
hop がネットワークに出るのは GitHub の固定アドレスへの HTTPS リクエストだけで、
|
|
178
|
-
タイムアウトとサイズ上限つき。
|
|
60
|
+
picker とそのキー、`hop root <branch>` などは [ガイド](docs/guide.ja.md#使い方) にある。
|
|
179
61
|
|
|
180
|
-
##
|
|
62
|
+
## 安全がデフォルト
|
|
181
63
|
|
|
182
|
-
`hop
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
64
|
+
- `hop rm` は、`--force` を付けない限り dirty な worktree を拒否する。`hop clean` と `hop root` は dirty な worktree に触れない。
|
|
65
|
+
- git が locked と報告する worktree は、`--force` でも削除しない。
|
|
66
|
+
- `_worktree/<repo>/` の外にある (external) worktree を picker から削除するときは y/N を確認する。Ctrl+X は常に確認する。
|
|
67
|
+
- `hop clean` が既定で対象にするのは、hop が管理する worktree だけ。external は `--ext` を付けたときだけ対象になる。
|
|
68
|
+
- `hop rm` は branch を残す。
|
|
186
69
|
|
|
187
|
-
|
|
188
|
-
export HOP_GIT=/path/to/git
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
## Agent skill (Claude Code / Codex plugin)
|
|
70
|
+
## AI coding agent 向け
|
|
192
71
|
|
|
193
|
-
|
|
194
|
-
[`using-hop`](skills/using-hop/SKILL.md) skill を配布する —
|
|
195
|
-
coding agent に `hop` を非対話・安全に扱う方法を教える skill。
|
|
72
|
+
全コマンドが非対話で完結する。`--json` はどこでも使え、path は stdout、ログは stderr に出る。このリポジトリは [`using-hop`](plugins/hop/skills/using-hop/SKILL.md) skill を plugin として配布している。agent が `hop` を安全に使うための skill。
|
|
196
73
|
|
|
197
74
|
```sh
|
|
198
75
|
# Claude Code
|
|
@@ -204,11 +81,10 @@ codex plugin marketplace add n-seiji/nuthatch
|
|
|
204
81
|
codex plugin install hop
|
|
205
82
|
```
|
|
206
83
|
|
|
207
|
-
##
|
|
208
|
-
|
|
209
|
-
- [docs/design.md](docs/design.md) — 設計書 (全文)
|
|
210
|
-
- [AGENTS.md](AGENTS.md) — このリポジトリで作業する coding agent 向けガイド
|
|
211
|
-
|
|
212
|
-
## License
|
|
84
|
+
## その他
|
|
213
85
|
|
|
214
|
-
|
|
86
|
+
- [Web サイト](https://www.sijis.me/projects/nuthatch)
|
|
87
|
+
- [ガイド](docs/guide.ja.md): 使い方、picker、インストール、更新、トラブルシューティング
|
|
88
|
+
- [docs/design.md](docs/design.md): 設計書
|
|
89
|
+
- [AGENTS.md](AGENTS.md): このリポジトリで作業する coding agent 向けガイド
|
|
90
|
+
- [LICENSE](LICENSE): GPL-3.0
|
package/README.md
CHANGED
|
@@ -1,204 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
hop - # cd back to the previous worktree
|
|
32
|
-
|
|
33
|
-
hop ls [--json] # list worktrees (dirty, ahead/behind, kind)
|
|
34
|
-
hop rm <branch> # remove a worktree (branch is kept) — managed or external, dirty needs --force
|
|
35
|
-
hop clean # auto-detect and remove garbage worktrees (managed only)
|
|
36
|
-
hop root <branch> # temporarily switch the root clone (for verification) —
|
|
37
|
-
# swaps out a clean, unlocked holder if the branch is checked out elsewhere
|
|
38
|
-
hop root - # switch the root clone back (only root's branch; a swapped-out holder stays detached)
|
|
39
|
-
|
|
40
|
-
hop -- <branch> # escape a branch name that collides with a reserved command
|
|
41
|
-
# (also required for a branch named the same as your `hop` binary/alias, e.g. `hop -- hop`)
|
|
42
|
-
hop --help # print usage (also -h / hop help)
|
|
43
|
-
|
|
44
|
-
hop --version # print hop's version
|
|
45
|
-
hop --update # replace a standalone binary with the latest release (other installs: prints how to update)
|
|
46
|
-
hop --update --check # only report whether an update is available — changes nothing
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
If Git reports the same branch checked out in multiple worktrees, the
|
|
50
|
-
branch-only `hop rm` command refuses rather than guessing which path to
|
|
51
|
-
remove. The interactive picker keeps the selected path through the final
|
|
52
|
-
lock-protected safety check.
|
|
53
|
-
|
|
54
|
-
### Interactive picker
|
|
55
|
-
|
|
56
|
-
The picker groups candidates into sections — existing worktrees (root first)
|
|
57
|
-
and not-yet-created branches — with a status marker, aligned columns, and
|
|
58
|
-
shortened paths:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
WORKTREES
|
|
62
|
-
❯ ● main root ~/ghq/.../nuthatch
|
|
63
|
-
○ feat/picker managed …/_worktree/feat__picker
|
|
64
|
-
● codex/fix-x ext …/.claude/worktrees/x
|
|
65
|
-
|
|
66
|
-
BRANCHES — Enter creates a worktree
|
|
67
|
-
+ feat/idea local
|
|
68
|
-
+ origin/hotfix remote
|
|
69
|
-
|
|
70
|
-
(●=dirty ○=clean +=not created)
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
A section (header included) disappears entirely when it has no candidates —
|
|
74
|
-
including when a search query filters it down to zero.
|
|
75
|
-
|
|
76
|
-
Ordering within each section is fixed, not insertion order: WORKTREES puts
|
|
77
|
-
root first, then managed worktrees, then external ones (branch name
|
|
78
|
-
ascending within each group, detached-HEAD worktrees last within theirs);
|
|
79
|
-
BRANCHES puts local branches before remote ones (branch name ascending
|
|
80
|
-
within each group). This holds under search filtering too — narrowing the
|
|
81
|
-
list never reshuffles what's left. BRANCHES rows render dim so "already a
|
|
82
|
-
worktree" vs. "not created yet" reads at a glance, on top of the ○/●/+
|
|
83
|
-
markers.
|
|
84
|
-
|
|
85
|
-
The picker runs in the terminal's alternate screen buffer (the same
|
|
86
|
-
mechanism fzf and vim use), so it never gets pushed into your scrollback
|
|
87
|
-
history — each run paints over itself and cleanly restores your shell's
|
|
88
|
-
screen on exit, however it exits (selection, Esc/Ctrl-C, or an interrupt).
|
|
89
|
-
|
|
90
|
-
### Interactive picker keys
|
|
91
|
-
|
|
92
|
-
The action panel renders as a column beside the candidate list (not an
|
|
93
|
-
overlay), so the list stays visible while you pick an action. On terminals
|
|
94
|
-
too narrow to fit both (under about 94 columns) it falls back to stacking
|
|
95
|
-
below the list instead.
|
|
96
|
-
|
|
97
|
-
| Key | Action |
|
|
98
|
-
|---|---|
|
|
99
|
-
| `Enter` | cd into the selected candidate |
|
|
100
|
-
| `Tab`, `→`, `Ctrl+L`, `Ctrl+F` | Open the action panel for the selected candidate (cd / delete / switch root here) |
|
|
101
|
-
| `Ctrl+X` | Delete the selected worktree (always asks y/N first) |
|
|
102
|
-
| `Ctrl+R` | Switch the root clone to the selected branch (asks y/N first for an `external` worktree) |
|
|
103
|
-
| `↑`/`↓`, `Ctrl+P`/`Ctrl+N`, `Ctrl+K` | Move the selection (arrow, emacs, and vim keys all work side by side) |
|
|
104
|
-
| `Esc` | Cancel quietly — exit 0, stdout stays empty (the shell wrapper just doesn't cd) |
|
|
105
|
-
| `Ctrl+C` | Cancel like an interrupt — exit 130, same as a real SIGINT |
|
|
106
|
-
|
|
107
|
-
Inside the action panel: the same up/down movement keys (left/right are
|
|
108
|
-
reserved for closing, so they never double as movement), `Enter` to run the
|
|
109
|
-
highlighted action, `c`/`d`/`r` to run cd/delete/switch-root directly, and
|
|
110
|
-
`Esc`, `Tab`, `←`, or `Ctrl+H` to close it back to the list (`←`/`Ctrl+H`
|
|
111
|
-
mirror the `→`/`Ctrl+L`/`Ctrl+F` that open it; `Tab` toggles either way —
|
|
112
|
-
handy on terminals like Ghostty that remap a chord such as Cmd+K to Tab). `delete`
|
|
113
|
-
appears for any already-created worktree (`managed` or `external` — from the
|
|
114
|
-
panel, deleting an `external` worktree, or a prunable one (git can no longer
|
|
115
|
-
find its working tree), asks y/N first; Ctrl+X always asks); `switch
|
|
116
|
-
root here` doesn't appear on the root worktree itself, and also asks y/N
|
|
117
|
-
first for an `external` worktree, since it can detach that worktree's HEAD.
|
|
118
|
-
Deleting reloads the
|
|
119
|
-
candidate list so you can keep deleting without leaving the picker; cd and
|
|
120
|
-
switch-root exit and print the resulting path, per hop's stdout contract.
|
|
121
|
-
|
|
122
|
-
Shell integration (auto-`cd`):
|
|
123
|
-
|
|
124
|
-
```sh
|
|
125
|
-
# ~/.zshrc
|
|
126
|
-
eval "$(hop init zsh)"
|
|
127
|
-
```
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/logo-dark.svg">
|
|
3
|
+
<img src="docs/assets/logo-light.svg" alt="nuthatch" width="360">
|
|
4
|
+
</picture>
|
|
5
|
+
|
|
6
|
+
**English** · [日本語](README.ja.md)
|
|
7
|
+
|
|
8
|
+
**Create, jump between, and remove git worktrees. All with `hop`.**
|
|
9
|
+
|
|
10
|
+
`nuthatch` is a zero-setup git worktree manager for humans and AI coding agents. Its one command, `hop`, needs no config and no state files. [Website](https://www.sijis.me/en/projects/nuthatch)
|
|
11
|
+
|
|
12
|
+
<table>
|
|
13
|
+
<tr>
|
|
14
|
+
<td align="center" valign="top" width="33%">
|
|
15
|
+
<img src="docs/assets/tile-create.svg" width="200" alt="Create a worktree"><br>
|
|
16
|
+
<b>Create</b><br>
|
|
17
|
+
<sub>The branch and its worktree, on the spot.</sub>
|
|
18
|
+
</td>
|
|
19
|
+
<td align="center" valign="top" width="33%">
|
|
20
|
+
<img src="docs/assets/tile-jump.svg" width="200" alt="Jump between worktrees"><br>
|
|
21
|
+
<b>Jump</b><br>
|
|
22
|
+
<sub><code>hop</code> to pick, <code>hop -</code> to go back.</sub>
|
|
23
|
+
</td>
|
|
24
|
+
<td align="center" valign="top" width="33%">
|
|
25
|
+
<img src="docs/assets/tile-remove.svg" width="200" alt="Remove a worktree"><br>
|
|
26
|
+
<b>Remove</b><br>
|
|
27
|
+
<sub>One at a time, or clean up what's merged.</sub>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
</table>
|
|
128
31
|
|
|
129
32
|
## Install
|
|
130
33
|
|
|
131
|
-
Recommended: the GitHub Release binary (built with bun; via mise,
|
|
132
|
-
`mise use github:n-seiji/nuthatch`). The npm version runs on Node, so its git
|
|
133
|
-
calls are slower (hop ls ~400ms vs ~60ms) — prefer the binary for CI or
|
|
134
|
-
frequent invocations.
|
|
135
|
-
|
|
136
34
|
```sh
|
|
137
|
-
# Prebuilt binary (macOS arm64/x64, Linux x64) — no Node.js required, fastest:
|
|
138
35
|
curl -fsSL https://raw.githubusercontent.com/n-seiji/nuthatch/main/install.sh | sh
|
|
139
|
-
mise use github:n-seiji/nuthatch # mise, via the GitHub Release binary
|
|
140
|
-
|
|
141
|
-
# npm (slower: shells out to git via Node — hop ls ~400ms vs ~60ms for the binary)
|
|
142
|
-
npm i -g @n-seiji/nuthatch # or: bunx @n-seiji/nuthatch
|
|
143
|
-
mise use -g npm:@n-seiji/nuthatch # mise, via npm
|
|
144
36
|
```
|
|
145
37
|
|
|
146
|
-
|
|
147
|
-
`HOP_INSTALL_DIR`) and always fetches the latest GitHub Release; pin a
|
|
148
|
-
specific version with `HOP_VERSION=vX.Y.Z`. Linux arm64 has no prebuilt
|
|
149
|
-
binary yet — use the npm install instead.
|
|
150
|
-
|
|
151
|
-
Release binaries from releases after v0.2.0 carry a build-provenance
|
|
152
|
-
attestation. To verify a downloaded binary by hand:
|
|
38
|
+
Then add this to `~/.zshrc` so `hop` can `cd` for you:
|
|
153
39
|
|
|
154
40
|
```sh
|
|
155
|
-
|
|
156
|
-
--signer-workflow github.com/n-seiji/nuthatch/.github/workflows/release.yml
|
|
41
|
+
eval "$(hop init zsh)"
|
|
157
42
|
```
|
|
158
43
|
|
|
159
|
-
|
|
44
|
+
For npm, mise, Linux arm64, verifying binaries, and updating, see the [guide](docs/guide.md#install).
|
|
160
45
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
```sh
|
|
164
|
-
hop --update # replace a standalone binary with the latest release
|
|
165
|
-
hop --update --check # just report: current, latest, how hop was installed, what to do
|
|
166
|
-
hop --version # which version do I have?
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
`hop --update` only replaces the binary that `install.sh` installs, and only if
|
|
170
|
-
its directory is writable (checked before the binary or its checksum is
|
|
171
|
-
downloaded) and its SHA-256 matches the `.sha256` attached to the release; on a
|
|
172
|
-
mismatch nothing is replaced (SHA-256 only; attestations are not checked). For any other install it runs nothing, makes no
|
|
173
|
-
request, and prints one line saying how to update it (exit 1):
|
|
46
|
+
## Usage
|
|
174
47
|
|
|
175
|
-
|
|
|
48
|
+
| Command | What it does |
|
|
176
49
|
|---|---|
|
|
177
|
-
| `
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
50
|
+
| `hop` | Pick a worktree or branch interactively and `cd` into it |
|
|
51
|
+
| `hop <branch>` | `cd` into the branch's worktree |
|
|
52
|
+
| `hop <branch> --create` | Create the worktree if it is missing |
|
|
53
|
+
| `hop -` | `cd` back to the previous worktree |
|
|
54
|
+
| `hop root` | `cd` into the root clone |
|
|
55
|
+
| `hop ls [--json]` | List worktrees (dirty, ahead/behind, kind) |
|
|
56
|
+
| `hop rm <branch>` | Remove a worktree. The branch is kept |
|
|
57
|
+
| `hop clean` | Find and remove garbage worktrees (managed only by default) |
|
|
58
|
+
| `hop --update` | Replace a standalone binary with the latest release |
|
|
181
59
|
|
|
182
|
-
|
|
183
|
-
only network traffic is HTTPS requests to fixed GitHub addresses, each with a
|
|
184
|
-
timeout and a size cap.
|
|
60
|
+
The [guide](docs/guide.md#usage) covers the picker and its keys, `hop root <branch>`, and the rest.
|
|
185
61
|
|
|
186
|
-
##
|
|
62
|
+
## Safe by default
|
|
187
63
|
|
|
188
|
-
`hop
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
64
|
+
- `hop rm` refuses a dirty worktree unless you pass `--force`. `hop clean` and `hop root` never touch one.
|
|
65
|
+
- A worktree that git reports as locked is never removed, even with `--force`.
|
|
66
|
+
- Deleting an external worktree (one not under `_worktree/<repo>/`) from the picker asks y/N. Ctrl+X always asks.
|
|
67
|
+
- By default, `hop clean` only picks worktrees that hop manages. External ones need `--ext`.
|
|
68
|
+
- `hop rm` keeps the branch.
|
|
192
69
|
|
|
193
|
-
|
|
194
|
-
export HOP_GIT=/path/to/git
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Agent skill (Claude Code / Codex plugin)
|
|
70
|
+
## For AI coding agents
|
|
198
71
|
|
|
199
|
-
|
|
200
|
-
[`using-hop`](skills/using-hop/SKILL.md) skill — it teaches coding agents how
|
|
201
|
-
to drive `hop` non-interactively and safely.
|
|
72
|
+
Every command can complete non-interactively. `--json` works everywhere, paths go to stdout, and logs go to stderr. The repo ships the [`using-hop`](plugins/hop/skills/using-hop/SKILL.md) skill as a plugin. It teaches agents to drive `hop` safely.
|
|
202
73
|
|
|
203
74
|
```sh
|
|
204
75
|
# Claude Code
|
|
@@ -210,11 +81,10 @@ codex plugin marketplace add n-seiji/nuthatch
|
|
|
210
81
|
codex plugin install hop
|
|
211
82
|
```
|
|
212
83
|
|
|
213
|
-
##
|
|
214
|
-
|
|
215
|
-
- [docs/design.md](docs/design.md) — full design document
|
|
216
|
-
- [AGENTS.md](AGENTS.md) — guide for coding agents working on this repo
|
|
217
|
-
|
|
218
|
-
## License
|
|
84
|
+
## More
|
|
219
85
|
|
|
220
|
-
|
|
86
|
+
- [Website](https://www.sijis.me/en/projects/nuthatch)
|
|
87
|
+
- [Guide](docs/guide.md): usage, picker, install, updating, troubleshooting
|
|
88
|
+
- [docs/design.md](docs/design.md): the design document
|
|
89
|
+
- [AGENTS.md](AGENTS.md): guide for coding agents working on this repo
|
|
90
|
+
- [LICENSE](LICENSE): GPL-3.0
|
|
@@ -37,7 +37,7 @@ var reportFatalError = (command, json, error) => {
|
|
|
37
37
|
// package.json
|
|
38
38
|
var package_default = {
|
|
39
39
|
name: "@n-seiji/nuthatch",
|
|
40
|
-
version: "0.2.
|
|
40
|
+
version: "0.2.2",
|
|
41
41
|
description: "git worktree manager (hop)",
|
|
42
42
|
homepage: "https://github.com/n-seiji/nuthatch#readme",
|
|
43
43
|
bugs: {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import"./cli-ph0gp56k.js";
|
|
2
2
|
import {
|
|
3
3
|
rm2
|
|
4
|
-
} from "./cli-
|
|
4
|
+
} from "./cli-vt5mddc4.js";
|
|
5
5
|
import {
|
|
6
6
|
root2
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-aj2y82fq.js";
|
|
8
8
|
import {
|
|
9
9
|
graphemes,
|
|
10
10
|
displayWidth,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
isOptionLikeRef,
|
|
27
27
|
loadRepoContext,
|
|
28
28
|
worktreeDirtyState
|
|
29
|
-
} from "./cli-
|
|
29
|
+
} from "./cli-4f1vz23g.js";
|
|
30
30
|
import {
|
|
31
31
|
EXIT_CANCELLED,
|
|
32
32
|
ok
|
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
wantsJson,
|
|
10
10
|
reportFatalError,
|
|
11
11
|
VERSION
|
|
12
|
-
} from "./cli-
|
|
12
|
+
} from "./cli-mx5n3y1f.js";
|
|
13
13
|
import {
|
|
14
14
|
GIT_EXECUTABLE_OVERRIDE_ENV,
|
|
15
15
|
GIT_NOT_FOUND_ERROR_CODE,
|
|
@@ -442,14 +442,14 @@ var getPorts = () => {
|
|
|
442
442
|
var JSON_FLAG = "json";
|
|
443
443
|
var runLs = async (json) => {
|
|
444
444
|
const [{ ls }, { reportLs }] = await Promise.all([
|
|
445
|
-
import("./ls-
|
|
445
|
+
import("./ls-hvwtaxv8.js"),
|
|
446
446
|
import("./render-ls-x4hpnsvy.js")
|
|
447
447
|
]);
|
|
448
448
|
const { git, fs } = getPorts();
|
|
449
449
|
reportLs(await ls(git, fs, { cwd: process.cwd() }), json);
|
|
450
450
|
};
|
|
451
451
|
var runJump = async (target, options) => {
|
|
452
|
-
const { jump } = await import("./jump-
|
|
452
|
+
const { jump } = await import("./jump-vrwxwb1f.js");
|
|
453
453
|
const { git, fs } = getPorts();
|
|
454
454
|
const result = await jump(git, fs, {
|
|
455
455
|
cwd: process.cwd(),
|
|
@@ -465,7 +465,7 @@ var runInteractivePick = async (json) => {
|
|
|
465
465
|
loadPickCandidates,
|
|
466
466
|
renderSwitchRootOutcome,
|
|
467
467
|
runInteractivePicker
|
|
468
|
-
} = await import("./cli-pick-
|
|
468
|
+
} = await import("./cli-pick-8qtma4v0.js");
|
|
469
469
|
const { git, fs } = getPorts();
|
|
470
470
|
const candidates = await loadPickCandidates(git, fs, json);
|
|
471
471
|
if (candidates === null) {
|
|
@@ -524,7 +524,7 @@ var runRm = async (tokens) => {
|
|
|
524
524
|
positionals: [{ name: "branch", required: true }],
|
|
525
525
|
booleans: ["force", JSON_FLAG]
|
|
526
526
|
});
|
|
527
|
-
const { rm } = await import("./rm-
|
|
527
|
+
const { rm } = await import("./rm-a5d1c4mh.js");
|
|
528
528
|
const { git, fs } = getPorts();
|
|
529
529
|
const result = await rm(git, fs, {
|
|
530
530
|
cwd: process.cwd(),
|
|
@@ -537,7 +537,7 @@ var runClean = async (tokens) => {
|
|
|
537
537
|
const args = parseCliArgs(tokens, {
|
|
538
538
|
booleans: ["ext", "withBranch", "dryRun", "yes", JSON_FLAG]
|
|
539
539
|
});
|
|
540
|
-
const { clean } = await import("./clean-
|
|
540
|
+
const { clean } = await import("./clean-6q90wtve.js");
|
|
541
541
|
const { git, fs, term } = getPorts();
|
|
542
542
|
const result = await clean(git, fs, term, {
|
|
543
543
|
cwd: process.cwd(),
|
|
@@ -554,7 +554,7 @@ var runRoot = async (tokens) => {
|
|
|
554
554
|
booleans: [JSON_FLAG],
|
|
555
555
|
strings: ["track"]
|
|
556
556
|
});
|
|
557
|
-
const { root } = await import("./root-
|
|
557
|
+
const { root } = await import("./root-zbrsb9md.js");
|
|
558
558
|
const { git, fs } = getPorts();
|
|
559
559
|
const { branch } = args.positionals;
|
|
560
560
|
const { track } = args.strings;
|
|
@@ -592,7 +592,7 @@ var dispatchAndRun = async (dispatch) => {
|
|
|
592
592
|
await (command === undefined ? runJumpFromArgs(dispatch.args) : command(dispatch.args));
|
|
593
593
|
};
|
|
594
594
|
var runUpdate = async (rawArgs) => {
|
|
595
|
-
const update = await import("./cli-update-
|
|
595
|
+
const update = await import("./cli-update-rjqr81xx.js");
|
|
596
596
|
await update.runUpdate(rawArgs, getPorts().term);
|
|
597
597
|
};
|
|
598
598
|
var main = async () => {
|
package/package.json
CHANGED
|
@@ -12,14 +12,14 @@ import {
|
|
|
12
12
|
classifyWorktreePath
|
|
13
13
|
} from "./cli-a56b2ar0.js";
|
|
14
14
|
|
|
15
|
+
// src/infra/repo.ts
|
|
16
|
+
import { basename, dirname, join } from "node:path";
|
|
17
|
+
|
|
15
18
|
// src/domain/branch-name.ts
|
|
16
19
|
var isOptionLikeRef = (name) => name.startsWith("-");
|
|
17
20
|
var optionLikeRefRejection = (label, name) => isOptionLikeRef(name) ? fail(EXIT_USAGE_ERROR, `Refusing ${label} "${name}": names starting with "-" would be parsed as git options.`) : null;
|
|
18
21
|
var refArgumentRejection = (branch, track) => optionLikeRefRejection("branch", branch) ?? (track === undefined ? null : optionLikeRefRejection("--track ref", track));
|
|
19
22
|
|
|
20
|
-
// src/infra/repo.ts
|
|
21
|
-
import { basename, dirname, join } from "node:path";
|
|
22
|
-
|
|
23
23
|
// src/domain/tracking.ts
|
|
24
24
|
var resolveTrackingRef = (branch, remotes) => {
|
|
25
25
|
if (remotes.includes("origin")) {
|