@cestoliv/wt 0.5.0-pr24.g7ccdc37 → 0.5.0
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.md +11 -42
- package/SKILL.md +8 -35
- package/dist/{agent-CSBQ4HUU.js → agent-Z3YCY245.js} +6 -17
- package/dist/{chunk-OUWQ6NIV.js → chunk-FNAMNRUH.js} +1 -3
- package/dist/{chunk-47P7FKFX.js → chunk-GHYUCETL.js} +15 -175
- package/dist/{chunk-S75RK5RZ.js → chunk-QGSJG72F.js} +2 -2
- package/dist/cli.js +9 -14
- package/dist/{config-QSYG3JDC.js → config-RFATE2PF.js} +1 -1
- package/dist/{create-L3JURY7V.js → create-XKF574AL.js} +3 -3
- package/dist/list-XHV4ODXW.js +204 -0
- package/dist/skill-MVKLVB5V.js +9 -0
- package/package.json +1 -1
- package/dist/chunk-63BGDDDM.js +0 -271
- package/dist/list-TTNX2HBM.js +0 -19
- package/dist/prune-NAGNNGQ5.js +0 -31
- package/dist/skill-GZVGKD5Z.js +0 -9
package/README.md
CHANGED
|
@@ -37,8 +37,7 @@ infer from this project. Write the result to the config file (find its path with
|
|
|
37
37
|
wt # Browse worktrees (interactive TUI)
|
|
38
38
|
wt create my-feat # New worktree, opens your IDE
|
|
39
39
|
wt agent my-feat "Plan the feature" # New worktree + AI agent in Zed (macOS)
|
|
40
|
-
wt agent fix-bug "Fix bug" --mode auto # Use auto mode instead of
|
|
41
|
-
wt prune # Remove merged worktrees (per-branch confirm)
|
|
40
|
+
wt agent fix-bug "Fix bug" --mode auto # Use auto mode instead of plan
|
|
42
41
|
wt config # Edit config in $EDITOR
|
|
43
42
|
wt skill # Print the skill file (for AI agents)
|
|
44
43
|
```
|
|
@@ -52,15 +51,14 @@ wt agent refactor "Refactor API layer" --mode default
|
|
|
52
51
|
```
|
|
53
52
|
|
|
54
53
|
Creates a worktree exactly like `wt create`, then auto-starts your agent
|
|
55
|
-
(default `claude
|
|
56
|
-
|
|
54
|
+
(default `claude --permission-mode plan`) in Zed's integrated terminal —
|
|
55
|
+
pre-filled with your prompt and left interactive for you to take over.
|
|
57
56
|
|
|
58
|
-
**Available modes** (`--mode`, defaults to `
|
|
59
|
-
the `agent_mode` config key):
|
|
57
|
+
**Available modes** (`--mode`, defaults to `plan`):
|
|
60
58
|
|
|
61
|
-
- `default` — Standard interactive mode with approval for each action
|
|
59
|
+
- `default` — Standard interactive mode with approval for each action
|
|
62
60
|
- `acceptEdits` — Allow file changes but keep command execution controlled
|
|
63
|
-
- `plan` — Architecture-first mode with no surprise mutations
|
|
61
|
+
- `plan` — Architecture-first mode with no surprise mutations (default)
|
|
64
62
|
- `auto` — Claude's safety model makes decisions instead of prompting
|
|
65
63
|
- `dontAsk` — Minimal interruptions in trusted environments
|
|
66
64
|
- `bypassPermissions` — Skip all permission checks (dangerous, CI/sandbox only)
|
|
@@ -87,12 +85,12 @@ An interactive, fuzzy-searchable list of your worktrees:
|
|
|
87
85
|
|
|
88
86
|
```
|
|
89
87
|
MY-PROJECT
|
|
90
|
-
▶ main
|
|
88
|
+
▶ main ~/dev/my-project
|
|
91
89
|
fix: resolve auth bug (2h ago)
|
|
92
90
|
feat/dashboard ~/dev/my-project-feat-dashboard
|
|
93
91
|
wip: add chart component (1d ago)
|
|
94
92
|
|
|
95
|
-
↕ navigate · Enter open · D delete ·
|
|
93
|
+
↕ navigate · Enter open · D delete · C create · A agent · Q quit
|
|
96
94
|
```
|
|
97
95
|
|
|
98
96
|
Type to fuzzy-filter branches instantly. Inside a repo it shows that repo's
|
|
@@ -106,12 +104,8 @@ and a permission mode — **worktree (repo → branch) → plan prompt → permi
|
|
|
106
104
|
mode**. Pressing `Esc` steps back to the previous question (answers preserved),
|
|
107
105
|
or back to the list from the first step. After creating, the list **refreshes
|
|
108
106
|
and stays open** (preserving your search and cursor) instead of exiting — only
|
|
109
|
-
`Enter` and `Q`/`Esc` leave the TUI. `
|
|
110
|
-
|
|
111
|
-
`a`/`c`/`d`/`p` are command keys, so they can't be typed into the search box.
|
|
112
|
-
|
|
113
|
-
The main worktree is tagged `(main)` and is protected — `D` only removes linked
|
|
114
|
-
worktrees, never the main repository.
|
|
107
|
+
`Enter` and `Q`/`Esc` leave the TUI. Note that `a`/`c`/`d` are command keys, so
|
|
108
|
+
they can't be typed into the search box.
|
|
115
109
|
|
|
116
110
|
## Create — `wt create [branch]`
|
|
117
111
|
|
|
@@ -127,29 +121,6 @@ registered repos.
|
|
|
127
121
|
If the path already exists, `wt create` offers to open it in your IDE instead of
|
|
128
122
|
erroring (in a non-interactive shell it exits non-zero).
|
|
129
123
|
|
|
130
|
-
## Prune — `wt prune`
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
wt prune # remove every merged worktree, one confirmation per branch
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Cleans up the worktrees you're done with: it finds every worktree whose branch
|
|
137
|
-
has already been merged into the base branch (`base_branch`, default
|
|
138
|
-
`origin/main`) and removes it — **always confirming each branch individually**,
|
|
139
|
-
and force-confirming when git refuses (submodules or uncommitted changes), just
|
|
140
|
-
like a manual `D` delete. The branch itself stays; only the worktree is removed.
|
|
141
|
-
Your `teardown_commands` run before each removal.
|
|
142
|
-
|
|
143
|
-
Merge detection covers both **fast-forward / merge-commit** merges (the branch
|
|
144
|
-
tip is an ancestor of the base branch) and patch-id matches (via `git cherry`,
|
|
145
|
-
so a single-commit branch **squash-merged** through a PR is still detected); a
|
|
146
|
-
worktree still sitting exactly on the base commit is never offered. `wt prune`
|
|
147
|
-
best-effort fetches
|
|
148
|
-
the remote first; if the base ref can't be resolved (offline, missing), it
|
|
149
|
-
removes nothing. The TUI exposes the same action under the `P` key. Works in
|
|
150
|
-
repo mode and across all registered repos in global mode (each against its own
|
|
151
|
-
`base_branch`).
|
|
152
|
-
|
|
153
124
|
## Configuration
|
|
154
125
|
|
|
155
126
|
Edit with `wt config` (`wt config --path` prints the file location —
|
|
@@ -163,10 +134,8 @@ Edit with `wt config` (`wt config --path` prints the file location —
|
|
|
163
134
|
| `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
|
|
164
135
|
| `setup_commands` | `[]` | Commands to run in new worktrees |
|
|
165
136
|
| `teardown_commands` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`) |
|
|
166
|
-
| `agent_command` | `"claude"`
|
|
167
|
-
| `agent_mode` | `"default"` | Default permission mode for `wt agent` (overridden by `--mode`) |
|
|
137
|
+
| `agent_command` | `"claude --permission-mode plan"` | Base command; `--permission-mode` replaced by `--mode` option, then prompt appended |
|
|
168
138
|
| `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs and presses |
|
|
169
|
-
| `auto_refresh_minutes`| `5` | How often the interactive list re-fetches worktrees (shows a "last refreshed" header); `0` disables it |
|
|
170
139
|
| `repo_overrides` | `{}` | Per-repo overrides for any key above |
|
|
171
140
|
|
|
172
141
|
Override any key per repo:
|
package/SKILL.md
CHANGED
|
@@ -17,8 +17,7 @@ Launch the interactive TUI. Shows worktrees for the current repo (repo mode) or
|
|
|
17
17
|
|
|
18
18
|
- Arrow keys — navigate
|
|
19
19
|
- `Enter` — open worktree in IDE (exits the TUI)
|
|
20
|
-
- `D` — delete worktree
|
|
21
|
-
- `P` — prune all merged worktrees (per-branch confirmation)
|
|
20
|
+
- `D` — delete worktree
|
|
22
21
|
- `C` — create a new worktree (works in both repo and global mode)
|
|
23
22
|
- `A` — create a worktree and start an AI agent in it (works in both modes)
|
|
24
23
|
- type to search · `Backspace` — edit search
|
|
@@ -39,8 +38,8 @@ After a create or agent action the TUI **refreshes and stays open** on the list
|
|
|
39
38
|
(your search and cursor are preserved) rather than exiting — only `Enter` (open)
|
|
40
39
|
and `Q`/`Esc` exit.
|
|
41
40
|
|
|
42
|
-
Because `a`/`A`, `c`/`C`, `d`/`D
|
|
43
|
-
|
|
41
|
+
Because `a`/`A`, `c`/`C`, and `d`/`D` are reserved as command keys, those
|
|
42
|
+
letters can't be typed into the search box.
|
|
44
43
|
|
|
45
44
|
### `wt create [branch]`
|
|
46
45
|
|
|
@@ -66,12 +65,11 @@ wt agent feature/fix 'Fix the bug in payment processing' --mode auto
|
|
|
66
65
|
wt agent refactor/api 'Refactor the API layer' --mode default
|
|
67
66
|
```
|
|
68
67
|
|
|
69
|
-
The `--mode` flag sets Claude Code's permission mode (defaults to `
|
|
70
|
-
change the default with the `agent_mode` config key):
|
|
68
|
+
The `--mode` flag sets Claude Code's permission mode (defaults to `plan`):
|
|
71
69
|
|
|
72
|
-
- `default` — Standard interactive mode with approval for each action
|
|
70
|
+
- `default` — Standard interactive mode with approval for each action
|
|
73
71
|
- `acceptEdits` — Allow file changes but keep command execution controlled
|
|
74
|
-
- `plan` — Architecture-first mode with no surprise mutations
|
|
72
|
+
- `plan` — Architecture-first mode with no surprise mutations (default)
|
|
75
73
|
- `auto` — Claude's safety model makes decisions instead of prompting
|
|
76
74
|
- `dontAsk` — Minimal interruptions in trusted environments
|
|
77
75
|
- `bypassPermissions` — Skip all permission checks (dangerous, CI/sandbox only)
|
|
@@ -99,29 +97,6 @@ If the worktree path already exists, `wt agent` prompts you to **open it in the
|
|
|
99
97
|
IDE**, **open it and start the agent**, or **quit** — instead of erroring. (In a
|
|
100
98
|
non-interactive shell it errors with a non-zero exit instead of prompting.)
|
|
101
99
|
|
|
102
|
-
### `wt prune`
|
|
103
|
-
|
|
104
|
-
Remove every worktree whose branch has already been merged into the base
|
|
105
|
-
branch (`base_branch`, default `origin/main`). Each candidate is confirmed
|
|
106
|
-
individually — and force-confirmed when git refuses (submodules / uncommitted
|
|
107
|
-
changes), exactly like a manual `d` delete. The branch itself is left intact;
|
|
108
|
-
only the worktree is removed.
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
wt prune # review and remove merged worktrees, one prompt per branch
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
Merge detection covers two cases: a branch whose tip is an ancestor of the base
|
|
115
|
-
branch (a **fast-forward / merge-commit** merge — its commits live verbatim in
|
|
116
|
-
base), and a branch whose diff already exists in base by patch id (via
|
|
117
|
-
`git cherry`, so a single-commit branch **squash-merged** through a PR is still
|
|
118
|
-
recognized). A worktree still sitting exactly on the base commit (no work yet) is
|
|
119
|
-
never offered. It also best-effort fetches the remote first so detection sees
|
|
120
|
-
up-to-date refs; if the
|
|
121
|
-
base ref can't be resolved (e.g. offline), nothing is removed. Works in repo
|
|
122
|
-
mode (current repo) and global mode (all registered repos, each against its own
|
|
123
|
-
`base_branch`). The TUI exposes the same action under the `p` key.
|
|
124
|
-
|
|
125
100
|
### `wt config`
|
|
126
101
|
|
|
127
102
|
Open the global config file in `$EDITOR` (defaults to `nano`).
|
|
@@ -149,16 +124,14 @@ Config is stored as JSON. Get the path with `wt config --path`.
|
|
|
149
124
|
| `teardown_commands` | `string[]` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`); on failure you are prompted whether to delete anyway |
|
|
150
125
|
| `ide` | `string` | `"zed"` | IDE command to open worktrees with |
|
|
151
126
|
| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |
|
|
152
|
-
| `agent_command` | `string` | `"claude"`
|
|
153
|
-
| `agent_mode` | `string` | `"default"` | Default Claude Code permission mode for `wt agent`; the `--mode` flag overrides it. One of `default`, `acceptEdits`, `plan`, `auto`, `dontAsk`, `bypassPermissions` |
|
|
127
|
+
| `agent_command` | `string` | `"claude --permission-mode plan"` | Base command `wt agent` runs in Zed; any `--permission-mode` flag is replaced by the `--mode` option (defaults to `plan`), then `<plan_prompt>` is appended single-quoted |
|
|
154
128
|
| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |
|
|
155
|
-
| `auto_refresh_minutes`| `number` | `5` | How often the interactive list (`wt`) re-fetches worktrees and updates the "last refreshed" header; `0` disables auto-refresh |
|
|
156
129
|
| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |
|
|
157
130
|
| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |
|
|
158
131
|
|
|
159
132
|
### Per-repo overrides
|
|
160
133
|
|
|
161
|
-
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `
|
|
134
|
+
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:
|
|
162
135
|
|
|
163
136
|
```json
|
|
164
137
|
{
|
|
@@ -3,9 +3,9 @@ import {
|
|
|
3
3
|
openConfiguredIde,
|
|
4
4
|
prepareWorktree,
|
|
5
5
|
promptExistingWorktree
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-QGSJG72F.js";
|
|
7
|
+
import "./chunk-GHYUCETL.js";
|
|
8
|
+
import "./chunk-FNAMNRUH.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/agent.ts
|
|
11
11
|
import * as clack from "@clack/prompts";
|
|
@@ -424,30 +424,19 @@ var VALID_MODES = [
|
|
|
424
424
|
"dontAsk",
|
|
425
425
|
"bypassPermissions"
|
|
426
426
|
];
|
|
427
|
-
var isValidMode = (mode) => VALID_MODES.includes(mode);
|
|
428
427
|
var CLEANUP_DELAY_MS = 2e4;
|
|
429
428
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
430
429
|
async function createAgentWorktree(branch, planPrompt, options = {}) {
|
|
431
|
-
|
|
430
|
+
const mode = options.mode ?? "plan";
|
|
431
|
+
if (!VALID_MODES.includes(mode)) {
|
|
432
432
|
console.error(
|
|
433
|
-
pc.red(
|
|
434
|
-
`Invalid mode "${options.mode}". Valid modes: ${VALID_MODES.join(", ")}`
|
|
435
|
-
)
|
|
433
|
+
pc.red(`Invalid mode "${mode}". Valid modes: ${VALID_MODES.join(", ")}`)
|
|
436
434
|
);
|
|
437
435
|
process.exit(1);
|
|
438
436
|
}
|
|
439
437
|
const prepared = await prepareWorktree(branch, options);
|
|
440
438
|
if (!prepared) return;
|
|
441
439
|
const { status, config, worktreePath } = prepared;
|
|
442
|
-
let mode = options.mode ?? config.agent_mode ?? "default";
|
|
443
|
-
if (!isValidMode(mode)) {
|
|
444
|
-
console.warn(
|
|
445
|
-
pc.yellow(
|
|
446
|
-
`\u26A0 Invalid agent_mode "${mode}" in config; using "default". Valid modes: ${VALID_MODES.join(", ")}`
|
|
447
|
-
)
|
|
448
|
-
);
|
|
449
|
-
mode = "default";
|
|
450
|
-
}
|
|
451
440
|
if (status === "exists") {
|
|
452
441
|
const prompt = options.existingWorktreePrompt ?? promptExistingWorktree;
|
|
453
442
|
const action = await prompt(worktreePath, { allowAgent: true });
|
|
@@ -68,10 +68,8 @@ var DEFAULT_CONFIG = {
|
|
|
68
68
|
teardown_commands: [],
|
|
69
69
|
ide: "zed",
|
|
70
70
|
ide_open_args: ["-n"],
|
|
71
|
-
agent_command: "claude",
|
|
71
|
+
agent_command: "claude --permission-mode plan",
|
|
72
72
|
agent_trigger_chord: "ctrl-shift-cmd-c",
|
|
73
|
-
auto_refresh_minutes: 5,
|
|
74
|
-
agent_mode: "default",
|
|
75
73
|
repos: [],
|
|
76
74
|
repo_overrides: {}
|
|
77
75
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createStore,
|
|
4
4
|
getGlobalConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FNAMNRUH.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/git.ts
|
|
8
8
|
import { execFileSync } from "child_process";
|
|
@@ -48,7 +48,7 @@ function listWorktrees(repoRoot, cwd = process.cwd()) {
|
|
|
48
48
|
}));
|
|
49
49
|
}
|
|
50
50
|
function parseWorktreeList(output, repoRoot, cwd) {
|
|
51
|
-
return output.trim().split("\n\n").map((block
|
|
51
|
+
return output.trim().split("\n\n").map((block) => {
|
|
52
52
|
const lines = block.trim().split("\n");
|
|
53
53
|
const wtPath = lines[0].slice("worktree ".length);
|
|
54
54
|
const branchLine = lines.find((l) => l.startsWith("branch "));
|
|
@@ -57,8 +57,6 @@ function parseWorktreeList(output, repoRoot, cwd) {
|
|
|
57
57
|
path: wtPath,
|
|
58
58
|
branch,
|
|
59
59
|
isCurrent: cwd === wtPath || cwd.startsWith(wtPath + path.sep),
|
|
60
|
-
// The main worktree is always the first entry of `git worktree list`.
|
|
61
|
-
isMain: index === 0,
|
|
62
60
|
repoRoot
|
|
63
61
|
};
|
|
64
62
|
});
|
|
@@ -79,16 +77,6 @@ function addWorktree(repoRoot, worktreePath, branch, baseBranch) {
|
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
79
|
function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
82
|
-
const resolve = (p) => {
|
|
83
|
-
try {
|
|
84
|
-
return realpathSync(p);
|
|
85
|
-
} catch {
|
|
86
|
-
return p;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
if (resolve(worktreePath) === resolve(repoRoot)) {
|
|
90
|
-
throw new Error("Refusing to remove the main worktree");
|
|
91
|
-
}
|
|
92
80
|
try {
|
|
93
81
|
execFileSync(
|
|
94
82
|
"git",
|
|
@@ -138,40 +126,6 @@ function branchExists(repoRoot, branch) {
|
|
|
138
126
|
return false;
|
|
139
127
|
}
|
|
140
128
|
}
|
|
141
|
-
function isAncestor(repoRoot, ancestor, descendant) {
|
|
142
|
-
try {
|
|
143
|
-
execFileSync("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
|
|
144
|
-
cwd: repoRoot,
|
|
145
|
-
stdio: "pipe"
|
|
146
|
-
});
|
|
147
|
-
return true;
|
|
148
|
-
} catch {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
function isBranchMerged(repoRoot, branch, baseBranch) {
|
|
153
|
-
try {
|
|
154
|
-
const revParse = (ref) => execFileSync("git", ["rev-parse", ref], {
|
|
155
|
-
cwd: repoRoot,
|
|
156
|
-
encoding: "utf8",
|
|
157
|
-
stdio: "pipe"
|
|
158
|
-
}).trim();
|
|
159
|
-
const baseTip = revParse(baseBranch);
|
|
160
|
-
const tip = revParse(branch);
|
|
161
|
-
if (tip !== baseTip && isAncestor(repoRoot, branch, baseBranch)) {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
const out = execFileSync("git", ["cherry", baseBranch, branch], {
|
|
165
|
-
cwd: repoRoot,
|
|
166
|
-
encoding: "utf8",
|
|
167
|
-
stdio: "pipe"
|
|
168
|
-
});
|
|
169
|
-
const lines = out.split("\n").filter((l) => l.trim().length > 0);
|
|
170
|
-
return lines.length > 0 && lines.every((l) => l.startsWith("-"));
|
|
171
|
-
} catch {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
129
|
function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
176
130
|
try {
|
|
177
131
|
execFileSync(
|
|
@@ -299,24 +253,8 @@ function shortenPath(p) {
|
|
|
299
253
|
const home = process.env.HOME ?? "";
|
|
300
254
|
return home && p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
301
255
|
}
|
|
302
|
-
function
|
|
303
|
-
const time = lastRefresh.toLocaleTimeString();
|
|
304
|
-
return `\u27F3 Last refreshed ${time} \xB7 every ${intervalMinutes}m`;
|
|
305
|
-
}
|
|
306
|
-
function reconcileSelectedIndex(items, prevPath, prevIndex) {
|
|
307
|
-
if (items.length === 0) return 0;
|
|
308
|
-
if (prevPath) {
|
|
309
|
-
const found = items.findIndex((w) => w.path === prevPath);
|
|
310
|
-
if (found !== -1) return found;
|
|
311
|
-
}
|
|
312
|
-
return Math.min(Math.max(0, prevIndex), items.length - 1);
|
|
313
|
-
}
|
|
314
|
-
function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null, intervalMinutes = 0) {
|
|
256
|
+
function buildListLayout(items, selectedIndex, query, mode) {
|
|
315
257
|
const header = [];
|
|
316
|
-
if (lastRefresh && intervalMinutes > 0) {
|
|
317
|
-
header.push(pc.dim(formatRefreshStatus(lastRefresh, intervalMinutes)));
|
|
318
|
-
header.push("");
|
|
319
|
-
}
|
|
320
258
|
if (mode === "global") {
|
|
321
259
|
header.push(
|
|
322
260
|
pc.dim("\u2139 Not in a git repository \u2014 showing all registered worktrees")
|
|
@@ -333,7 +271,7 @@ function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null,
|
|
|
333
271
|
for (const item of groupItems) {
|
|
334
272
|
const start = body.length;
|
|
335
273
|
const cursor = i === selectedIndex ? pc.cyan("\u25B6") : " ";
|
|
336
|
-
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) :
|
|
274
|
+
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) : pc.white(item.branch);
|
|
337
275
|
const pathLabel = pc.dim(shortenPath(item.path));
|
|
338
276
|
body.push(` ${cursor} ${branchLabel} ${pathLabel}`);
|
|
339
277
|
if (item.lastCommit) {
|
|
@@ -344,33 +282,28 @@ function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null,
|
|
|
344
282
|
}
|
|
345
283
|
}
|
|
346
284
|
const footer = [
|
|
347
|
-
pc.dim(
|
|
348
|
-
"\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 P prune \xB7 C create \xB7 A agent \xB7 Q quit"
|
|
349
|
-
)
|
|
285
|
+
pc.dim("\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 C create \xB7 A agent \xB7 Q quit")
|
|
350
286
|
];
|
|
351
287
|
return { header, body, footer, itemSpans };
|
|
352
288
|
}
|
|
353
|
-
function clampScroll(offset, span,
|
|
354
|
-
const maxOffset = Math.max(0, bodyLength -
|
|
289
|
+
function clampScroll(offset, span, viewportHeight, bodyLength) {
|
|
290
|
+
const maxOffset = Math.max(0, bodyLength - viewportHeight);
|
|
355
291
|
let next = offset;
|
|
356
292
|
if (span.start < next) next = span.start;
|
|
357
|
-
if (span.end >= next +
|
|
293
|
+
if (span.end >= next + viewportHeight) next = span.end - viewportHeight + 1;
|
|
358
294
|
return Math.max(0, Math.min(next, maxOffset));
|
|
359
295
|
}
|
|
360
|
-
function composeView(layout, offset,
|
|
296
|
+
function composeView(layout, offset, viewportHeight) {
|
|
361
297
|
const { header, body, footer } = layout;
|
|
362
|
-
const visible = body.slice(offset, offset +
|
|
363
|
-
while (visible.length <
|
|
298
|
+
const visible = body.slice(offset, offset + viewportHeight);
|
|
299
|
+
while (visible.length < viewportHeight) visible.push("");
|
|
364
300
|
const topSlot = offset > 0 ? pc.dim(" \u2191 more") : "";
|
|
365
|
-
const bottomSlot = offset +
|
|
301
|
+
const bottomSlot = offset + viewportHeight < body.length ? pc.dim(" \u2193 more") : "";
|
|
366
302
|
return [...header, topSlot, ...visible, bottomSlot, ...footer].join("\n");
|
|
367
303
|
}
|
|
368
304
|
function fixedHeight(layout) {
|
|
369
305
|
return layout.header.length + layout.footer.length + 2;
|
|
370
306
|
}
|
|
371
|
-
function viewportHeight(layout, rows) {
|
|
372
|
-
return Math.max(1, rows - fixedHeight(layout) - 1);
|
|
373
|
-
}
|
|
374
307
|
function setupRawMode() {
|
|
375
308
|
process.stdin.setRawMode(true);
|
|
376
309
|
process.stdin.resume();
|
|
@@ -381,17 +314,6 @@ function cleanupRawMode() {
|
|
|
381
314
|
process.stdin.pause();
|
|
382
315
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
383
316
|
}
|
|
384
|
-
function waitForKeypress() {
|
|
385
|
-
return new Promise((resolve) => {
|
|
386
|
-
process.stdin.setRawMode(true);
|
|
387
|
-
process.stdin.resume();
|
|
388
|
-
process.stdin.once("data", () => {
|
|
389
|
-
process.stdin.setRawMode(false);
|
|
390
|
-
process.stdin.pause();
|
|
391
|
-
resolve();
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
317
|
function renderRepoPicker(repos, selectedIndex, query) {
|
|
396
318
|
const lines = [];
|
|
397
319
|
lines.push(pc.dim("\u2139 Not in a git repository \u2014 select a repo to create in"));
|
|
@@ -540,25 +462,15 @@ async function runWizard(steps) {
|
|
|
540
462
|
}
|
|
541
463
|
return true;
|
|
542
464
|
}
|
|
543
|
-
async function runInteractiveList(allItems, mode, handlers
|
|
544
|
-
const { autoRefreshMinutes = 0 } = options;
|
|
545
|
-
const refreshEnabled = Number.isFinite(autoRefreshMinutes) && autoRefreshMinutes > 0;
|
|
465
|
+
async function runInteractiveList(allItems, mode, handlers) {
|
|
546
466
|
let query = "";
|
|
547
467
|
let selectedIndex = 0;
|
|
548
468
|
let scrollOffset = 0;
|
|
549
469
|
let filtered = allItems;
|
|
550
|
-
let lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : null;
|
|
551
470
|
const render = () => {
|
|
552
471
|
const rows = process.stdout.rows ?? 24;
|
|
553
|
-
const layout = buildListLayout(
|
|
554
|
-
|
|
555
|
-
selectedIndex,
|
|
556
|
-
query,
|
|
557
|
-
mode,
|
|
558
|
-
lastRefresh,
|
|
559
|
-
autoRefreshMinutes
|
|
560
|
-
);
|
|
561
|
-
const viewport = viewportHeight(layout, rows);
|
|
472
|
+
const layout = buildListLayout(filtered, selectedIndex, query, mode);
|
|
473
|
+
const viewport = Math.max(1, rows - fixedHeight(layout));
|
|
562
474
|
const span = layout.itemSpans[selectedIndex] ?? { start: 0, end: 0 };
|
|
563
475
|
scrollOffset = clampScroll(
|
|
564
476
|
scrollOffset,
|
|
@@ -573,9 +485,6 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
573
485
|
render();
|
|
574
486
|
return new Promise((resolve, reject) => {
|
|
575
487
|
let listenerActive = false;
|
|
576
|
-
let interacting = false;
|
|
577
|
-
let refreshing = false;
|
|
578
|
-
let refreshTimer = null;
|
|
579
488
|
const attachListener = () => {
|
|
580
489
|
if (!listenerActive) {
|
|
581
490
|
process.stdin.on("data", onData);
|
|
@@ -588,35 +497,9 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
588
497
|
process.stdout.removeListener("resize", render);
|
|
589
498
|
listenerActive = false;
|
|
590
499
|
};
|
|
591
|
-
const stopRefresh = () => {
|
|
592
|
-
if (refreshTimer !== null) {
|
|
593
|
-
clearInterval(refreshTimer);
|
|
594
|
-
refreshTimer = null;
|
|
595
|
-
}
|
|
596
|
-
};
|
|
597
|
-
const tick = async () => {
|
|
598
|
-
if (interacting || refreshing) return;
|
|
599
|
-
refreshing = true;
|
|
600
|
-
try {
|
|
601
|
-
const prevPath = filtered[selectedIndex]?.path;
|
|
602
|
-
allItems = await handlers.refreshItems();
|
|
603
|
-
filtered = filterItems(allItems, query);
|
|
604
|
-
selectedIndex = reconcileSelectedIndex(
|
|
605
|
-
filtered,
|
|
606
|
-
prevPath,
|
|
607
|
-
selectedIndex
|
|
608
|
-
);
|
|
609
|
-
lastRefresh = /* @__PURE__ */ new Date();
|
|
610
|
-
if (!interacting) render();
|
|
611
|
-
} catch {
|
|
612
|
-
} finally {
|
|
613
|
-
refreshing = false;
|
|
614
|
-
}
|
|
615
|
-
};
|
|
616
500
|
const onData = async (key) => {
|
|
617
501
|
try {
|
|
618
502
|
if (key === "" || key === "q" || key === "Q" || key === "\x1B") {
|
|
619
|
-
stopRefresh();
|
|
620
503
|
detachListener();
|
|
621
504
|
cleanupRawMode();
|
|
622
505
|
resolve();
|
|
@@ -629,7 +512,6 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
629
512
|
} else if (key === "\r") {
|
|
630
513
|
const item = filtered[selectedIndex];
|
|
631
514
|
if (item) {
|
|
632
|
-
stopRefresh();
|
|
633
515
|
detachListener();
|
|
634
516
|
cleanupRawMode();
|
|
635
517
|
handlers.onOpen(item);
|
|
@@ -638,21 +520,12 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
638
520
|
} else if (key === "d" || key === "D") {
|
|
639
521
|
const item = filtered[selectedIndex];
|
|
640
522
|
if (item) {
|
|
641
|
-
if (item.isMain) {
|
|
642
|
-
process.stdout.write(
|
|
643
|
-
pc.red(
|
|
644
|
-
"\nCannot delete the main repository \u2014 only worktrees can be removed.\n"
|
|
645
|
-
)
|
|
646
|
-
);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
523
|
if (item.isCurrent) {
|
|
650
524
|
process.stdout.write(
|
|
651
525
|
pc.red("\nCannot delete the worktree you are currently in.\n")
|
|
652
526
|
);
|
|
653
527
|
return;
|
|
654
528
|
}
|
|
655
|
-
interacting = true;
|
|
656
529
|
detachListener();
|
|
657
530
|
cleanupRawMode();
|
|
658
531
|
const confirmed = await handlers.onDelete(item);
|
|
@@ -666,32 +539,9 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
666
539
|
);
|
|
667
540
|
setupRawMode();
|
|
668
541
|
attachListener();
|
|
669
|
-
interacting = false;
|
|
670
542
|
render();
|
|
671
543
|
}
|
|
672
|
-
} else if (key === "p" || key === "P") {
|
|
673
|
-
interacting = true;
|
|
674
|
-
detachListener();
|
|
675
|
-
cleanupRawMode();
|
|
676
|
-
const removed = await handlers.onWipe(allItems);
|
|
677
|
-
if (removed.length > 0) {
|
|
678
|
-
const removedSet = new Set(removed);
|
|
679
|
-
allItems = allItems.filter((w) => !removedSet.has(w));
|
|
680
|
-
filtered = filtered.filter((w) => !removedSet.has(w));
|
|
681
|
-
} else {
|
|
682
|
-
process.stdout.write(pc.dim("\nPress any key to continue\u2026"));
|
|
683
|
-
await waitForKeypress();
|
|
684
|
-
}
|
|
685
|
-
selectedIndex = Math.min(
|
|
686
|
-
selectedIndex,
|
|
687
|
-
Math.max(0, filtered.length - 1)
|
|
688
|
-
);
|
|
689
|
-
setupRawMode();
|
|
690
|
-
attachListener();
|
|
691
|
-
interacting = false;
|
|
692
|
-
render();
|
|
693
544
|
} else if (key === "c" || key === "C") {
|
|
694
|
-
interacting = true;
|
|
695
545
|
detachListener();
|
|
696
546
|
cleanupRawMode();
|
|
697
547
|
await handlers.onCreate();
|
|
@@ -701,13 +551,10 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
701
551
|
selectedIndex,
|
|
702
552
|
Math.max(0, filtered.length - 1)
|
|
703
553
|
);
|
|
704
|
-
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
705
554
|
setupRawMode();
|
|
706
555
|
attachListener();
|
|
707
|
-
interacting = false;
|
|
708
556
|
render();
|
|
709
557
|
} else if (key === "a" || key === "A") {
|
|
710
|
-
interacting = true;
|
|
711
558
|
detachListener();
|
|
712
559
|
cleanupRawMode();
|
|
713
560
|
await handlers.onAgent();
|
|
@@ -717,10 +564,8 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
717
564
|
selectedIndex,
|
|
718
565
|
Math.max(0, filtered.length - 1)
|
|
719
566
|
);
|
|
720
|
-
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
721
567
|
setupRawMode();
|
|
722
568
|
attachListener();
|
|
723
|
-
interacting = false;
|
|
724
569
|
render();
|
|
725
570
|
} else if (key === "\x7F") {
|
|
726
571
|
query = query.slice(0, -1);
|
|
@@ -736,16 +581,12 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
736
581
|
render();
|
|
737
582
|
}
|
|
738
583
|
} catch (err) {
|
|
739
|
-
stopRefresh();
|
|
740
584
|
detachListener();
|
|
741
585
|
cleanupRawMode();
|
|
742
586
|
reject(err);
|
|
743
587
|
}
|
|
744
588
|
};
|
|
745
589
|
attachListener();
|
|
746
|
-
if (refreshEnabled) {
|
|
747
|
-
refreshTimer = setInterval(tick, autoRefreshMinutes * 6e4);
|
|
748
|
-
}
|
|
749
590
|
});
|
|
750
591
|
}
|
|
751
592
|
|
|
@@ -756,7 +597,6 @@ export {
|
|
|
756
597
|
removeWorktree,
|
|
757
598
|
listWorktreeDirtyFiles,
|
|
758
599
|
branchExists,
|
|
759
|
-
isBranchMerged,
|
|
760
600
|
setUpstreamTracking,
|
|
761
601
|
fetchRemote,
|
|
762
602
|
resolveWorktreePath,
|
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
runCommands,
|
|
14
14
|
runRepoPicker,
|
|
15
15
|
setUpstreamTracking
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-GHYUCETL.js";
|
|
17
17
|
import {
|
|
18
18
|
createStore,
|
|
19
19
|
getEffectiveConfig
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-FNAMNRUH.js";
|
|
21
21
|
|
|
22
22
|
// src/commands/create.ts
|
|
23
23
|
import { existsSync } from "fs";
|
package/dist/cli.js
CHANGED
|
@@ -3,40 +3,35 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
var program = new Command();
|
|
6
|
-
program.name("wt").description("Git worktree manager").version("0.5.0
|
|
7
|
-
const { runList } = await import("./list-
|
|
6
|
+
program.name("wt").description("Git worktree manager").version("0.5.0").action(async () => {
|
|
7
|
+
const { runList } = await import("./list-XHV4ODXW.js");
|
|
8
8
|
await runList();
|
|
9
9
|
});
|
|
10
10
|
program.command("create [branch]").description("Create a new worktree").action(async (branch) => {
|
|
11
|
-
const { createWorktree } = await import("./create-
|
|
11
|
+
const { createWorktree } = await import("./create-XKF574AL.js");
|
|
12
12
|
await createWorktree(branch);
|
|
13
13
|
});
|
|
14
14
|
program.command("agent <branch> <plan_prompt>").description("Create a worktree and auto-start an AI agent in Zed (macOS)").option(
|
|
15
15
|
"--mode <mode>",
|
|
16
|
-
"Claude Code permission mode (default, plan, auto, etc.)
|
|
16
|
+
"Claude Code permission mode (default, plan, auto, etc.)",
|
|
17
|
+
"plan"
|
|
17
18
|
).action(
|
|
18
19
|
async (branch, planPrompt, options) => {
|
|
19
|
-
const { createAgentWorktree } = await import("./agent-
|
|
20
|
+
const { createAgentWorktree } = await import("./agent-Z3YCY245.js");
|
|
20
21
|
await createAgentWorktree(branch, planPrompt, { mode: options.mode });
|
|
21
22
|
}
|
|
22
23
|
);
|
|
23
|
-
program.command("prune").description(
|
|
24
|
-
"Remove worktrees whose branch has been merged into the base branch"
|
|
25
|
-
).action(async () => {
|
|
26
|
-
const { runPrune } = await import("./prune-NAGNNGQ5.js");
|
|
27
|
-
await runPrune();
|
|
28
|
-
});
|
|
29
24
|
program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
|
|
30
25
|
if (options.path) {
|
|
31
|
-
const { printConfigPath } = await import("./config-
|
|
26
|
+
const { printConfigPath } = await import("./config-RFATE2PF.js");
|
|
32
27
|
printConfigPath();
|
|
33
28
|
} else {
|
|
34
|
-
const { openConfig } = await import("./config-
|
|
29
|
+
const { openConfig } = await import("./config-RFATE2PF.js");
|
|
35
30
|
openConfig();
|
|
36
31
|
}
|
|
37
32
|
});
|
|
38
33
|
program.command("skill").description("Print the wt skill file to stdout").action(async () => {
|
|
39
|
-
const { printSkill } = await import("./skill-
|
|
34
|
+
const { printSkill } = await import("./skill-MVKLVB5V.js");
|
|
40
35
|
printSkill();
|
|
41
36
|
});
|
|
42
37
|
await program.parseAsync(process.argv);
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
openConfiguredIde,
|
|
5
5
|
prepareWorktree,
|
|
6
6
|
promptExistingWorktree
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-QGSJG72F.js";
|
|
8
|
+
import "./chunk-GHYUCETL.js";
|
|
9
|
+
import "./chunk-FNAMNRUH.js";
|
|
10
10
|
export {
|
|
11
11
|
createWorktree,
|
|
12
12
|
openConfiguredIde,
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getRegisteredRepos,
|
|
4
|
+
getRepoRoot,
|
|
5
|
+
listWorktreeDirtyFiles,
|
|
6
|
+
listWorktrees,
|
|
7
|
+
openIde,
|
|
8
|
+
registerRepo,
|
|
9
|
+
removeWorktree,
|
|
10
|
+
runBranchInput,
|
|
11
|
+
runCommands,
|
|
12
|
+
runInteractiveList,
|
|
13
|
+
runRepoPicker,
|
|
14
|
+
runWizard
|
|
15
|
+
} from "./chunk-GHYUCETL.js";
|
|
16
|
+
import {
|
|
17
|
+
createStore,
|
|
18
|
+
getEffectiveConfig
|
|
19
|
+
} from "./chunk-FNAMNRUH.js";
|
|
20
|
+
|
|
21
|
+
// src/commands/list.ts
|
|
22
|
+
import * as clack from "@clack/prompts";
|
|
23
|
+
import pc from "picocolors";
|
|
24
|
+
function buildWorktreeSteps(repoRoot, store, state) {
|
|
25
|
+
const steps = [];
|
|
26
|
+
if (!repoRoot) {
|
|
27
|
+
const repos = getRegisteredRepos(store);
|
|
28
|
+
steps.push(async () => {
|
|
29
|
+
const picked = await runRepoPicker(repos, state.pickedRepo);
|
|
30
|
+
if (!picked) return false;
|
|
31
|
+
state.pickedRepo = picked;
|
|
32
|
+
return true;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
steps.push(async () => {
|
|
36
|
+
const entered = await runBranchInput(
|
|
37
|
+
state.pickedRepo,
|
|
38
|
+
state.branch ?? ""
|
|
39
|
+
);
|
|
40
|
+
if (!entered) return false;
|
|
41
|
+
state.branch = entered;
|
|
42
|
+
return true;
|
|
43
|
+
});
|
|
44
|
+
return steps;
|
|
45
|
+
}
|
|
46
|
+
async function prepareListItems(options = {}) {
|
|
47
|
+
const { cwd = process.cwd(), store = createStore() } = options;
|
|
48
|
+
let repoRoot = null;
|
|
49
|
+
try {
|
|
50
|
+
repoRoot = getRepoRoot(cwd);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
if (repoRoot) {
|
|
54
|
+
registerRepo(repoRoot, store);
|
|
55
|
+
const items2 = listWorktrees(repoRoot, cwd);
|
|
56
|
+
return { items: items2, mode: "repo", repoRoot };
|
|
57
|
+
}
|
|
58
|
+
const repos = getRegisteredRepos(store);
|
|
59
|
+
const items = repos.flatMap((repo) => {
|
|
60
|
+
try {
|
|
61
|
+
return listWorktrees(repo, cwd);
|
|
62
|
+
} catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return { items, mode: "global", repoRoot: null };
|
|
67
|
+
}
|
|
68
|
+
async function runList(options = {}) {
|
|
69
|
+
const { store = createStore(), cwd = process.cwd() } = options;
|
|
70
|
+
const { items, mode, repoRoot } = await prepareListItems({ cwd, store });
|
|
71
|
+
if (items.length === 0 && mode === "global") {
|
|
72
|
+
console.log(
|
|
73
|
+
pc.dim(
|
|
74
|
+
"No repos registered. Run `wt create` inside a repo to get started."
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await runInteractiveList(items, mode, {
|
|
80
|
+
onOpen: (item) => {
|
|
81
|
+
const config = getEffectiveConfig(item.repoRoot, store);
|
|
82
|
+
openIde(config.ide, config.ide_open_args, item.path);
|
|
83
|
+
},
|
|
84
|
+
onDelete: async (item) => {
|
|
85
|
+
const confirmed = await clack.confirm({
|
|
86
|
+
message: `Remove worktree ${pc.bold(item.branch)}? This cannot be undone.`
|
|
87
|
+
});
|
|
88
|
+
if (clack.isCancel(confirmed) || !confirmed) return false;
|
|
89
|
+
const config = getEffectiveConfig(item.repoRoot, store);
|
|
90
|
+
if (config.teardown_commands.length > 0) {
|
|
91
|
+
console.log(pc.dim("Running teardown commands..."));
|
|
92
|
+
const result = await runCommands(config.teardown_commands, item.path);
|
|
93
|
+
if (!result.success) {
|
|
94
|
+
clack.log.warn(
|
|
95
|
+
`Teardown command failed: ${result.failedCommand} (exit code ${result.exitCode})`
|
|
96
|
+
);
|
|
97
|
+
const proceed = await clack.confirm({
|
|
98
|
+
message: `Delete ${pc.bold(item.branch)} anyway?`
|
|
99
|
+
});
|
|
100
|
+
if (clack.isCancel(proceed) || !proceed) return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
removeWorktree(item.repoRoot, item.path);
|
|
105
|
+
console.log(pc.green(`\u2713 Removed ${item.branch}`));
|
|
106
|
+
return true;
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const msg = String(err);
|
|
109
|
+
if (msg.includes("cannot be moved or removed")) {
|
|
110
|
+
clack.log.warn(
|
|
111
|
+
"Worktree contains git submodules, which prevent standard removal."
|
|
112
|
+
);
|
|
113
|
+
const force = await clack.confirm({
|
|
114
|
+
message: `Force delete ${pc.bold(item.branch)}? The worktree directory will be removed directly.`
|
|
115
|
+
});
|
|
116
|
+
if (clack.isCancel(force) || !force) return false;
|
|
117
|
+
try {
|
|
118
|
+
removeWorktree(item.repoRoot, item.path, true);
|
|
119
|
+
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
120
|
+
return true;
|
|
121
|
+
} catch (err2) {
|
|
122
|
+
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (msg.includes("modified or untracked files")) {
|
|
127
|
+
const dirty = listWorktreeDirtyFiles(item.path);
|
|
128
|
+
if (dirty.length > 0) {
|
|
129
|
+
clack.log.warn(
|
|
130
|
+
`Worktree has uncommitted changes:
|
|
131
|
+
${dirty.map((f) => ` ${f}`).join("\n")}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const force = await clack.confirm({
|
|
135
|
+
message: `Force delete ${pc.bold(item.branch)}? All changes will be lost.`
|
|
136
|
+
});
|
|
137
|
+
if (clack.isCancel(force) || !force) return false;
|
|
138
|
+
try {
|
|
139
|
+
removeWorktree(item.repoRoot, item.path, true);
|
|
140
|
+
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
141
|
+
return true;
|
|
142
|
+
} catch (err2) {
|
|
143
|
+
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
console.error(pc.red(`\u2717 Failed to remove: ${msg}`));
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
onCreate: async () => {
|
|
152
|
+
const state = { pickedRepo: repoRoot ?? void 0 };
|
|
153
|
+
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
154
|
+
if (!await runWizard(steps)) return;
|
|
155
|
+
if (state.pickedRepo === void 0 || state.branch === void 0) return;
|
|
156
|
+
const { createWorktree } = await import("./create-XKF574AL.js");
|
|
157
|
+
await createWorktree(state.branch, { cwd: state.pickedRepo, store });
|
|
158
|
+
},
|
|
159
|
+
onAgent: async () => {
|
|
160
|
+
const { createAgentWorktree, VALID_MODES } = await import("./agent-Z3YCY245.js");
|
|
161
|
+
const state = {
|
|
162
|
+
pickedRepo: repoRoot ?? void 0,
|
|
163
|
+
mode: "plan"
|
|
164
|
+
};
|
|
165
|
+
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
166
|
+
steps.push(async () => {
|
|
167
|
+
const entered = await clack.text({
|
|
168
|
+
message: "Plan prompt for the agent:",
|
|
169
|
+
initialValue: state.plan,
|
|
170
|
+
validate: (v) => !v || v.length === 0 ? "Required" : void 0
|
|
171
|
+
});
|
|
172
|
+
if (clack.isCancel(entered)) return false;
|
|
173
|
+
state.plan = entered;
|
|
174
|
+
return true;
|
|
175
|
+
});
|
|
176
|
+
steps.push(async () => {
|
|
177
|
+
const chosen = await clack.select({
|
|
178
|
+
message: "Permission mode:",
|
|
179
|
+
initialValue: state.mode,
|
|
180
|
+
options: VALID_MODES.map((m) => ({ value: String(m), label: m }))
|
|
181
|
+
});
|
|
182
|
+
if (clack.isCancel(chosen)) return false;
|
|
183
|
+
state.mode = chosen;
|
|
184
|
+
return true;
|
|
185
|
+
});
|
|
186
|
+
if (!await runWizard(steps)) return;
|
|
187
|
+
if (state.pickedRepo === void 0 || state.branch === void 0 || state.plan === void 0)
|
|
188
|
+
return;
|
|
189
|
+
await createAgentWorktree(state.branch, state.plan, {
|
|
190
|
+
cwd: state.pickedRepo,
|
|
191
|
+
store,
|
|
192
|
+
mode: state.mode
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
refreshItems: async () => {
|
|
196
|
+
const refreshed = await prepareListItems({ cwd, store });
|
|
197
|
+
return refreshed.items;
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
export {
|
|
202
|
+
prepareListItems,
|
|
203
|
+
runList
|
|
204
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/commands/skill.ts
|
|
4
|
+
function printSkill() {
|
|
5
|
+
console.log('---\nname: wt-worktree-manager\ndescription: Use the wt CLI to create, browse, open, and delete git worktrees across repos. Use when the user asks to manage worktrees, create isolated branches, or configure worktree defaults.\n---\n\n# wt \u2014 Git Worktree Manager\n\n`wt` is a CLI for managing git worktrees. It provides an interactive TUI to browse, create, open in your IDE, and delete worktrees across multiple repos.\n\n## Commands\n\n### `wt` (no subcommand)\n\nLaunch the interactive TUI. Shows worktrees for the current repo (repo mode) or all registered repos (global mode, when run outside a repo).\n\n**Keybindings in the TUI:**\n\n- Arrow keys \u2014 navigate\n- `Enter` \u2014 open worktree in IDE (exits the TUI)\n- `D` \u2014 delete worktree\n- `C` \u2014 create a new worktree (works in both repo and global mode)\n- `A` \u2014 create a worktree and start an AI agent in it (works in both modes)\n- type to search \xB7 `Backspace` \u2014 edit search\n- `Q` / `Esc` \u2014 quit\n\n`C` and `A` are step-by-step wizards. In global mode (run from outside a repo /\n"home") they start by prompting for the repo (picker), then the branch; in repo\nmode the repo is fixed so they start at the branch. `A` then adds two more\nsteps:\n\n- `C` \u2014 **worktree (repo \u2192 branch)**\n- `A` \u2014 **worktree (repo \u2192 branch) \u2192 plan prompt \u2192 permission mode**\n\nPressing `Esc` at any step goes back to the previous step (your earlier answers\nare preserved); pressing `Esc` on the first step returns to the list.\n\nAfter a create or agent action the TUI **refreshes and stays open** on the list\n(your search and cursor are preserved) rather than exiting \u2014 only `Enter` (open)\nand `Q`/`Esc` exit.\n\nBecause `a`/`A`, `c`/`C`, and `d`/`D` are reserved as command keys, those\nletters can\'t be typed into the search box.\n\n### `wt create [branch]`\n\nCreate a new worktree. If `branch` is omitted, prompts interactively.\n\nThe worktree is created as a sibling directory to the repo: `<parent>/<repo-name>-<branch-name>`.\n\nAfter creation, `wt` runs any configured `setup_commands` and opens the worktree in your IDE.\n\nIf the worktree path already exists, `wt create` doesn\'t error \u2014 it prompts you\nto **open it in the IDE** or **quit**. (In a non-interactive shell it errors\nwith a non-zero exit instead of prompting.)\n\n### `wt agent <branch> <plan_prompt> [--mode <mode>]`\n\nCreate a worktree (same as `wt create`) **and** auto-start an AI agent in Zed\'s\nintegrated terminal, pre-filled with `<plan_prompt>` and left interactive for\nyou to take over.\n\n```bash\nwt agent feature/login \'Read the codebase, then propose a plan for login.\'\nwt agent feature/fix \'Fix the bug in payment processing\' --mode auto\nwt agent refactor/api \'Refactor the API layer\' --mode default\n```\n\nThe `--mode` flag sets Claude Code\'s permission mode (defaults to `plan`):\n\n- `default` \u2014 Standard interactive mode with approval for each action\n- `acceptEdits` \u2014 Allow file changes but keep command execution controlled\n- `plan` \u2014 Architecture-first mode with no surprise mutations (default)\n- `auto` \u2014 Claude\'s safety model makes decisions instead of prompting\n- `dontAsk` \u2014 Minimal interruptions in trusted environments\n- `bypassPermissions` \u2014 Skip all permission checks (dangerous, CI/sandbox only)\n\nIt writes a temporary `.zed/tasks.json` running\n`<agent_command> --permission-mode <mode> \'<plan_prompt>\'`, ensures a global Zed keymap chord\n(`agent_trigger_chord`) spawns that task, opens Zed, presses the chord via\n`osascript`, then removes the temporary task so the repo is left clean.\n\n**macOS + Zed only.** Requires Accessibility permission for the app that runs\n`wt` (Zed itself, when run from its integrated terminal). If it isn\'t granted,\n`wt agent` opens the _Privacy & Security \u2192 Accessibility_ settings pane and waits\nfor you to grant it and confirm, then retries automatically. On other platforms\n(or when `ide` is not `zed`) the worktree is still created and opened, but the\nagent is not auto-started.\n\nOver SSH it still works, provided the same user has an active graphical login on\nthe Mac: the keystroke is run inside the GUI session via Launch Services\n(`open -a Terminal` briefly flashes a Terminal window). Grant Accessibility to\nTerminal (not Zed) the first time. With no one logged in graphically there is\nnothing to drive, so it falls back to the manual "press the chord in Zed"\nmessage.\n\nIf the worktree path already exists, `wt agent` prompts you to **open it in the\nIDE**, **open it and start the agent**, or **quit** \u2014 instead of erroring. (In a\nnon-interactive shell it errors with a non-zero exit instead of prompting.)\n\n### `wt config`\n\nOpen the global config file in `$EDITOR` (defaults to `nano`).\n\n```bash\nwt config # open in editor\nwt config --path # print config file path only\n```\n\n### `wt skill`\n\nPrint this skill file to stdout. Useful for piping to agents or copying to a project.\n\n## Configuration\n\nConfig is stored as JSON. Get the path with `wt config --path`.\n\n### Schema\n\n| Key | Type | Default | Description |\n| --------------------- | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `worktree_path` | `string` | `"../"` | Where to place new worktrees, relative to the repo root |\n| `base_branch` | `string` | `"origin/main"` | Branch to base new worktrees on |\n| `setup_commands` | `string[]` | `[]` | Commands to run in a new worktree after creation (e.g. `["npm install"]`) |\n| `teardown_commands` | `string[]` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`); on failure you are prompted whether to delete anyway |\n| `ide` | `string` | `"zed"` | IDE command to open worktrees with |\n| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |\n| `agent_command` | `string` | `"claude --permission-mode plan"` | Base command `wt agent` runs in Zed; any `--permission-mode` flag is replaced by the `--mode` option (defaults to `plan`), then `<plan_prompt>` is appended single-quoted |\n| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |\n| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |\n| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |\n\n### Per-repo overrides\n\nOverride any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:\n\n```json\n{\n "base_branch": "origin/main",\n "ide": "zed",\n "repo_overrides": {\n "/path/to/my-repo": {\n "base_branch": "origin/develop",\n "setup_commands": ["npm install", "npm run build"]\n }\n }\n}\n```\n\n## Common workflows\n\n### Create a worktree for a new feature\n\n```bash\ncd /path/to/repo\nwt create feature/my-branch\n```\n\n### Configure setup commands for a repo\n\n```bash\nwt config\n# Then add to the JSON:\n# "repo_overrides": {\n# "/path/to/repo": {\n# "setup_commands": ["npm install"]\n# }\n# }\n```\n\n### Browse all worktrees across repos\n\nRun `wt` from any directory outside a git repo to see worktrees from all registered repos.\n');
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
printSkill
|
|
9
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-63BGDDDM.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
fetchRemote,
|
|
4
|
-
getRegisteredRepos,
|
|
5
|
-
getRepoRoot,
|
|
6
|
-
isBranchMerged,
|
|
7
|
-
listWorktreeDirtyFiles,
|
|
8
|
-
listWorktrees,
|
|
9
|
-
openIde,
|
|
10
|
-
registerRepo,
|
|
11
|
-
removeWorktree,
|
|
12
|
-
runBranchInput,
|
|
13
|
-
runCommands,
|
|
14
|
-
runInteractiveList,
|
|
15
|
-
runRepoPicker,
|
|
16
|
-
runWizard
|
|
17
|
-
} from "./chunk-47P7FKFX.js";
|
|
18
|
-
import {
|
|
19
|
-
createStore,
|
|
20
|
-
getEffectiveConfig,
|
|
21
|
-
getGlobalConfig
|
|
22
|
-
} from "./chunk-OUWQ6NIV.js";
|
|
23
|
-
|
|
24
|
-
// src/commands/list.ts
|
|
25
|
-
import * as clack from "@clack/prompts";
|
|
26
|
-
import pc from "picocolors";
|
|
27
|
-
function buildWorktreeSteps(repoRoot, store, state) {
|
|
28
|
-
const steps = [];
|
|
29
|
-
if (!repoRoot) {
|
|
30
|
-
const repos = getRegisteredRepos(store);
|
|
31
|
-
steps.push(async () => {
|
|
32
|
-
const picked = await runRepoPicker(repos, state.pickedRepo);
|
|
33
|
-
if (!picked) return false;
|
|
34
|
-
state.pickedRepo = picked;
|
|
35
|
-
return true;
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
steps.push(async () => {
|
|
39
|
-
const entered = await runBranchInput(
|
|
40
|
-
state.pickedRepo,
|
|
41
|
-
state.branch ?? ""
|
|
42
|
-
);
|
|
43
|
-
if (!entered) return false;
|
|
44
|
-
state.branch = entered;
|
|
45
|
-
return true;
|
|
46
|
-
});
|
|
47
|
-
return steps;
|
|
48
|
-
}
|
|
49
|
-
async function prepareListItems(options = {}) {
|
|
50
|
-
const { cwd = process.cwd(), store = createStore() } = options;
|
|
51
|
-
let repoRoot = null;
|
|
52
|
-
try {
|
|
53
|
-
repoRoot = getRepoRoot(cwd);
|
|
54
|
-
} catch {
|
|
55
|
-
}
|
|
56
|
-
if (repoRoot) {
|
|
57
|
-
registerRepo(repoRoot, store);
|
|
58
|
-
const items2 = listWorktrees(repoRoot, cwd);
|
|
59
|
-
return { items: items2, mode: "repo", repoRoot };
|
|
60
|
-
}
|
|
61
|
-
const repos = getRegisteredRepos(store);
|
|
62
|
-
const items = repos.flatMap((repo) => {
|
|
63
|
-
try {
|
|
64
|
-
return listWorktrees(repo, cwd);
|
|
65
|
-
} catch {
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
return { items, mode: "global", repoRoot: null };
|
|
70
|
-
}
|
|
71
|
-
async function runList(options = {}) {
|
|
72
|
-
const { store = createStore(), cwd = process.cwd() } = options;
|
|
73
|
-
const { items, mode, repoRoot } = await prepareListItems({ cwd, store });
|
|
74
|
-
if (items.length === 0 && mode === "global") {
|
|
75
|
-
console.log(
|
|
76
|
-
pc.dim(
|
|
77
|
-
"No repos registered. Run `wt create` inside a repo to get started."
|
|
78
|
-
)
|
|
79
|
-
);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const autoRefreshMinutes = mode === "repo" && repoRoot ? getEffectiveConfig(repoRoot, store).auto_refresh_minutes : getGlobalConfig(store).auto_refresh_minutes;
|
|
83
|
-
await runInteractiveList(
|
|
84
|
-
items,
|
|
85
|
-
mode,
|
|
86
|
-
{
|
|
87
|
-
onOpen: (item) => {
|
|
88
|
-
const config = getEffectiveConfig(item.repoRoot, store);
|
|
89
|
-
openIde(config.ide, config.ide_open_args, item.path);
|
|
90
|
-
},
|
|
91
|
-
onDelete: (item) => deleteWorktree(item, store),
|
|
92
|
-
onWipe: (items2) => wipeWorktrees(items2, store, { fetch: true }),
|
|
93
|
-
onCreate: async () => {
|
|
94
|
-
const state = { pickedRepo: repoRoot ?? void 0 };
|
|
95
|
-
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
96
|
-
if (!await runWizard(steps)) return;
|
|
97
|
-
if (state.pickedRepo === void 0 || state.branch === void 0)
|
|
98
|
-
return;
|
|
99
|
-
const { createWorktree } = await import("./create-L3JURY7V.js");
|
|
100
|
-
await createWorktree(state.branch, { cwd: state.pickedRepo, store });
|
|
101
|
-
},
|
|
102
|
-
onAgent: async () => {
|
|
103
|
-
const { createAgentWorktree, VALID_MODES } = await import("./agent-CSBQ4HUU.js");
|
|
104
|
-
const state = {
|
|
105
|
-
pickedRepo: repoRoot ?? void 0
|
|
106
|
-
};
|
|
107
|
-
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
108
|
-
steps.push(async () => {
|
|
109
|
-
const entered = await clack.text({
|
|
110
|
-
message: "Plan prompt for the agent:",
|
|
111
|
-
initialValue: state.plan,
|
|
112
|
-
validate: (v) => !v || v.length === 0 ? "Required" : void 0
|
|
113
|
-
});
|
|
114
|
-
if (clack.isCancel(entered)) return false;
|
|
115
|
-
state.plan = entered;
|
|
116
|
-
return true;
|
|
117
|
-
});
|
|
118
|
-
steps.push(async () => {
|
|
119
|
-
const configuredMode = state.pickedRepo ? getEffectiveConfig(state.pickedRepo, store).agent_mode : void 0;
|
|
120
|
-
const chosen = await clack.select({
|
|
121
|
-
message: "Permission mode:",
|
|
122
|
-
initialValue: state.mode ?? configuredMode,
|
|
123
|
-
options: VALID_MODES.map((m) => ({ value: String(m), label: m }))
|
|
124
|
-
});
|
|
125
|
-
if (clack.isCancel(chosen)) return false;
|
|
126
|
-
state.mode = chosen;
|
|
127
|
-
return true;
|
|
128
|
-
});
|
|
129
|
-
if (!await runWizard(steps)) return;
|
|
130
|
-
if (state.pickedRepo === void 0 || state.branch === void 0 || state.plan === void 0)
|
|
131
|
-
return;
|
|
132
|
-
await createAgentWorktree(state.branch, state.plan, {
|
|
133
|
-
cwd: state.pickedRepo,
|
|
134
|
-
store,
|
|
135
|
-
mode: state.mode
|
|
136
|
-
});
|
|
137
|
-
},
|
|
138
|
-
refreshItems: async () => {
|
|
139
|
-
const refreshed = await prepareListItems({ cwd, store });
|
|
140
|
-
return refreshed.items;
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
{ autoRefreshMinutes }
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
async function deleteWorktree(item, store) {
|
|
147
|
-
const confirmed = await clack.confirm({
|
|
148
|
-
message: `Remove worktree ${pc.bold(item.branch)}? This cannot be undone.`
|
|
149
|
-
});
|
|
150
|
-
if (clack.isCancel(confirmed) || !confirmed) return false;
|
|
151
|
-
const config = getEffectiveConfig(item.repoRoot, store);
|
|
152
|
-
if (config.teardown_commands.length > 0) {
|
|
153
|
-
console.log(pc.dim("Running teardown commands..."));
|
|
154
|
-
const result = await runCommands(config.teardown_commands, item.path);
|
|
155
|
-
if (!result.success) {
|
|
156
|
-
clack.log.warn(
|
|
157
|
-
`Teardown command failed: ${result.failedCommand} (exit code ${result.exitCode})`
|
|
158
|
-
);
|
|
159
|
-
const proceed = await clack.confirm({
|
|
160
|
-
message: `Delete ${pc.bold(item.branch)} anyway?`
|
|
161
|
-
});
|
|
162
|
-
if (clack.isCancel(proceed) || !proceed) return false;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
try {
|
|
166
|
-
removeWorktree(item.repoRoot, item.path);
|
|
167
|
-
console.log(pc.green(`\u2713 Removed ${item.branch}`));
|
|
168
|
-
return true;
|
|
169
|
-
} catch (err) {
|
|
170
|
-
const msg = String(err);
|
|
171
|
-
if (msg.includes("cannot be moved or removed")) {
|
|
172
|
-
clack.log.warn(
|
|
173
|
-
"Worktree contains git submodules, which prevent standard removal."
|
|
174
|
-
);
|
|
175
|
-
const force = await clack.confirm({
|
|
176
|
-
message: `Force delete ${pc.bold(item.branch)}? The worktree directory will be removed directly.`
|
|
177
|
-
});
|
|
178
|
-
if (clack.isCancel(force) || !force) return false;
|
|
179
|
-
try {
|
|
180
|
-
removeWorktree(item.repoRoot, item.path, true);
|
|
181
|
-
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
182
|
-
return true;
|
|
183
|
-
} catch (err2) {
|
|
184
|
-
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (msg.includes("modified or untracked files")) {
|
|
189
|
-
const dirty = listWorktreeDirtyFiles(item.path);
|
|
190
|
-
if (dirty.length > 0) {
|
|
191
|
-
clack.log.warn(
|
|
192
|
-
`Worktree has uncommitted changes:
|
|
193
|
-
${dirty.map((f) => ` ${f}`).join("\n")}`
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
const force = await clack.confirm({
|
|
197
|
-
message: `Force delete ${pc.bold(item.branch)}? All changes will be lost.`
|
|
198
|
-
});
|
|
199
|
-
if (clack.isCancel(force) || !force) return false;
|
|
200
|
-
try {
|
|
201
|
-
removeWorktree(item.repoRoot, item.path, true);
|
|
202
|
-
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
203
|
-
return true;
|
|
204
|
-
} catch (err2) {
|
|
205
|
-
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
console.error(pc.red(`\u2717 Failed to remove: ${msg}`));
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function selectWipeCandidates(items, isMerged) {
|
|
214
|
-
return items.filter(
|
|
215
|
-
(wt) => !wt.isCurrent && !wt.isMain && wt.branch !== "(detached)" && isMerged(wt)
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
function buildMergedPredicate(store) {
|
|
219
|
-
return (wt) => {
|
|
220
|
-
const config = getEffectiveConfig(wt.repoRoot, store);
|
|
221
|
-
const base = config.base_branch;
|
|
222
|
-
const baseLocal = base.split("/", 2).slice(1).join("/") || base;
|
|
223
|
-
if (wt.branch === base || wt.branch === baseLocal) return false;
|
|
224
|
-
return isBranchMerged(wt.repoRoot, wt.branch, base);
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
async function wipeWorktrees(items, store, options = {}) {
|
|
228
|
-
if (options.fetch) {
|
|
229
|
-
const seen = /* @__PURE__ */ new Set();
|
|
230
|
-
for (const wt of items) {
|
|
231
|
-
if (seen.has(wt.repoRoot)) continue;
|
|
232
|
-
seen.add(wt.repoRoot);
|
|
233
|
-
const parts = getEffectiveConfig(wt.repoRoot, store).base_branch.split(
|
|
234
|
-
"/",
|
|
235
|
-
2
|
|
236
|
-
);
|
|
237
|
-
if (parts.length !== 2) continue;
|
|
238
|
-
const remote = parts[0] || "origin";
|
|
239
|
-
try {
|
|
240
|
-
fetchRemote(wt.repoRoot, remote);
|
|
241
|
-
} catch (err) {
|
|
242
|
-
console.warn(
|
|
243
|
-
pc.yellow(
|
|
244
|
-
`\u26A0 Could not fetch from ${remote} \u2014 using local state${err instanceof Error ? ` (${err.message})` : ""}`
|
|
245
|
-
)
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const candidates = selectWipeCandidates(items, buildMergedPredicate(store));
|
|
251
|
-
if (candidates.length === 0) {
|
|
252
|
-
console.log(pc.dim("No merged worktrees to wipe."));
|
|
253
|
-
return [];
|
|
254
|
-
}
|
|
255
|
-
const removed = [];
|
|
256
|
-
for (const candidate of candidates) {
|
|
257
|
-
if (await deleteWorktree(candidate, store)) {
|
|
258
|
-
removed.push(candidate);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return removed;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export {
|
|
265
|
-
prepareListItems,
|
|
266
|
-
runList,
|
|
267
|
-
deleteWorktree,
|
|
268
|
-
selectWipeCandidates,
|
|
269
|
-
buildMergedPredicate,
|
|
270
|
-
wipeWorktrees
|
|
271
|
-
};
|
package/dist/list-TTNX2HBM.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
buildMergedPredicate,
|
|
4
|
-
deleteWorktree,
|
|
5
|
-
prepareListItems,
|
|
6
|
-
runList,
|
|
7
|
-
selectWipeCandidates,
|
|
8
|
-
wipeWorktrees
|
|
9
|
-
} from "./chunk-63BGDDDM.js";
|
|
10
|
-
import "./chunk-47P7FKFX.js";
|
|
11
|
-
import "./chunk-OUWQ6NIV.js";
|
|
12
|
-
export {
|
|
13
|
-
buildMergedPredicate,
|
|
14
|
-
deleteWorktree,
|
|
15
|
-
prepareListItems,
|
|
16
|
-
runList,
|
|
17
|
-
selectWipeCandidates,
|
|
18
|
-
wipeWorktrees
|
|
19
|
-
};
|
package/dist/prune-NAGNNGQ5.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
prepareListItems,
|
|
4
|
-
wipeWorktrees
|
|
5
|
-
} from "./chunk-63BGDDDM.js";
|
|
6
|
-
import "./chunk-47P7FKFX.js";
|
|
7
|
-
import {
|
|
8
|
-
createStore
|
|
9
|
-
} from "./chunk-OUWQ6NIV.js";
|
|
10
|
-
|
|
11
|
-
// src/commands/prune.ts
|
|
12
|
-
import pc from "picocolors";
|
|
13
|
-
async function runPrune(options = {}) {
|
|
14
|
-
const { cwd = process.cwd(), store = createStore() } = options;
|
|
15
|
-
const { items, mode } = await prepareListItems({ cwd, store });
|
|
16
|
-
if (items.length === 0) {
|
|
17
|
-
console.log(
|
|
18
|
-
pc.dim(
|
|
19
|
-
mode === "global" ? "No repos registered. Run `wt create` inside a repo to get started." : "No worktrees found."
|
|
20
|
-
)
|
|
21
|
-
);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const removed = await wipeWorktrees(items, store, { fetch: true });
|
|
25
|
-
if (removed.length > 0) {
|
|
26
|
-
console.log(pc.green(`\u2713 Pruned ${removed.length} worktree(s).`));
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
export {
|
|
30
|
-
runPrune
|
|
31
|
-
};
|
package/dist/skill-GZVGKD5Z.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/commands/skill.ts
|
|
4
|
-
function printSkill() {
|
|
5
|
-
console.log('---\nname: wt-worktree-manager\ndescription: Use the wt CLI to create, browse, open, and delete git worktrees across repos. Use when the user asks to manage worktrees, create isolated branches, or configure worktree defaults.\n---\n\n# wt \u2014 Git Worktree Manager\n\n`wt` is a CLI for managing git worktrees. It provides an interactive TUI to browse, create, open in your IDE, and delete worktrees across multiple repos.\n\n## Commands\n\n### `wt` (no subcommand)\n\nLaunch the interactive TUI. Shows worktrees for the current repo (repo mode) or all registered repos (global mode, when run outside a repo).\n\n**Keybindings in the TUI:**\n\n- Arrow keys \u2014 navigate\n- `Enter` \u2014 open worktree in IDE (exits the TUI)\n- `D` \u2014 delete worktree (the main worktree is tagged `(main)` and cannot be deleted \u2014 only linked worktrees can)\n- `P` \u2014 prune all merged worktrees (per-branch confirmation)\n- `C` \u2014 create a new worktree (works in both repo and global mode)\n- `A` \u2014 create a worktree and start an AI agent in it (works in both modes)\n- type to search \xB7 `Backspace` \u2014 edit search\n- `Q` / `Esc` \u2014 quit\n\n`C` and `A` are step-by-step wizards. In global mode (run from outside a repo /\n"home") they start by prompting for the repo (picker), then the branch; in repo\nmode the repo is fixed so they start at the branch. `A` then adds two more\nsteps:\n\n- `C` \u2014 **worktree (repo \u2192 branch)**\n- `A` \u2014 **worktree (repo \u2192 branch) \u2192 plan prompt \u2192 permission mode**\n\nPressing `Esc` at any step goes back to the previous step (your earlier answers\nare preserved); pressing `Esc` on the first step returns to the list.\n\nAfter a create or agent action the TUI **refreshes and stays open** on the list\n(your search and cursor are preserved) rather than exiting \u2014 only `Enter` (open)\nand `Q`/`Esc` exit.\n\nBecause `a`/`A`, `c`/`C`, `d`/`D`, and `p`/`P` are reserved as command keys,\nthose letters can\'t be typed into the search box.\n\n### `wt create [branch]`\n\nCreate a new worktree. If `branch` is omitted, prompts interactively.\n\nThe worktree is created as a sibling directory to the repo: `<parent>/<repo-name>-<branch-name>`.\n\nAfter creation, `wt` runs any configured `setup_commands` and opens the worktree in your IDE.\n\nIf the worktree path already exists, `wt create` doesn\'t error \u2014 it prompts you\nto **open it in the IDE** or **quit**. (In a non-interactive shell it errors\nwith a non-zero exit instead of prompting.)\n\n### `wt agent <branch> <plan_prompt> [--mode <mode>]`\n\nCreate a worktree (same as `wt create`) **and** auto-start an AI agent in Zed\'s\nintegrated terminal, pre-filled with `<plan_prompt>` and left interactive for\nyou to take over.\n\n```bash\nwt agent feature/login \'Read the codebase, then propose a plan for login.\'\nwt agent feature/fix \'Fix the bug in payment processing\' --mode auto\nwt agent refactor/api \'Refactor the API layer\' --mode default\n```\n\nThe `--mode` flag sets Claude Code\'s permission mode (defaults to `default`;\nchange the default with the `agent_mode` config key):\n\n- `default` \u2014 Standard interactive mode with approval for each action (default)\n- `acceptEdits` \u2014 Allow file changes but keep command execution controlled\n- `plan` \u2014 Architecture-first mode with no surprise mutations\n- `auto` \u2014 Claude\'s safety model makes decisions instead of prompting\n- `dontAsk` \u2014 Minimal interruptions in trusted environments\n- `bypassPermissions` \u2014 Skip all permission checks (dangerous, CI/sandbox only)\n\nIt writes a temporary `.zed/tasks.json` running\n`<agent_command> --permission-mode <mode> \'<plan_prompt>\'`, ensures a global Zed keymap chord\n(`agent_trigger_chord`) spawns that task, opens Zed, presses the chord via\n`osascript`, then removes the temporary task so the repo is left clean.\n\n**macOS + Zed only.** Requires Accessibility permission for the app that runs\n`wt` (Zed itself, when run from its integrated terminal). If it isn\'t granted,\n`wt agent` opens the _Privacy & Security \u2192 Accessibility_ settings pane and waits\nfor you to grant it and confirm, then retries automatically. On other platforms\n(or when `ide` is not `zed`) the worktree is still created and opened, but the\nagent is not auto-started.\n\nOver SSH it still works, provided the same user has an active graphical login on\nthe Mac: the keystroke is run inside the GUI session via Launch Services\n(`open -a Terminal` briefly flashes a Terminal window). Grant Accessibility to\nTerminal (not Zed) the first time. With no one logged in graphically there is\nnothing to drive, so it falls back to the manual "press the chord in Zed"\nmessage.\n\nIf the worktree path already exists, `wt agent` prompts you to **open it in the\nIDE**, **open it and start the agent**, or **quit** \u2014 instead of erroring. (In a\nnon-interactive shell it errors with a non-zero exit instead of prompting.)\n\n### `wt prune`\n\nRemove every worktree whose branch has already been merged into the base\nbranch (`base_branch`, default `origin/main`). Each candidate is confirmed\nindividually \u2014 and force-confirmed when git refuses (submodules / uncommitted\nchanges), exactly like a manual `d` delete. The branch itself is left intact;\nonly the worktree is removed.\n\n```bash\nwt prune # review and remove merged worktrees, one prompt per branch\n```\n\nMerge detection covers two cases: a branch whose tip is an ancestor of the base\nbranch (a **fast-forward / merge-commit** merge \u2014 its commits live verbatim in\nbase), and a branch whose diff already exists in base by patch id (via\n`git cherry`, so a single-commit branch **squash-merged** through a PR is still\nrecognized). A worktree still sitting exactly on the base commit (no work yet) is\nnever offered. It also best-effort fetches the remote first so detection sees\nup-to-date refs; if the\nbase ref can\'t be resolved (e.g. offline), nothing is removed. Works in repo\nmode (current repo) and global mode (all registered repos, each against its own\n`base_branch`). The TUI exposes the same action under the `p` key.\n\n### `wt config`\n\nOpen the global config file in `$EDITOR` (defaults to `nano`).\n\n```bash\nwt config # open in editor\nwt config --path # print config file path only\n```\n\n### `wt skill`\n\nPrint this skill file to stdout. Useful for piping to agents or copying to a project.\n\n## Configuration\n\nConfig is stored as JSON. Get the path with `wt config --path`.\n\n### Schema\n\n| Key | Type | Default | Description |\n| --------------------- | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `worktree_path` | `string` | `"../"` | Where to place new worktrees, relative to the repo root |\n| `base_branch` | `string` | `"origin/main"` | Branch to base new worktrees on |\n| `setup_commands` | `string[]` | `[]` | Commands to run in a new worktree after creation (e.g. `["npm install"]`) |\n| `teardown_commands` | `string[]` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`); on failure you are prompted whether to delete anyway |\n| `ide` | `string` | `"zed"` | IDE command to open worktrees with |\n| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |\n| `agent_command` | `string` | `"claude"` | Base command `wt agent` runs in Zed; `--permission-mode <mode>` is injected (any existing one replaced), then `<plan_prompt>` is appended single-quoted |\n| `agent_mode` | `string` | `"default"` | Default Claude Code permission mode for `wt agent`; the `--mode` flag overrides it. One of `default`, `acceptEdits`, `plan`, `auto`, `dontAsk`, `bypassPermissions` |\n| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |\n| `auto_refresh_minutes`| `number` | `5` | How often the interactive list (`wt`) re-fetches worktrees and updates the "last refreshed" header; `0` disables auto-refresh |\n| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |\n| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |\n\n### Per-repo overrides\n\nOverride any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_mode`, `agent_trigger_chord`, `auto_refresh_minutes`) for a specific repo:\n\n```json\n{\n "base_branch": "origin/main",\n "ide": "zed",\n "repo_overrides": {\n "/path/to/my-repo": {\n "base_branch": "origin/develop",\n "setup_commands": ["npm install", "npm run build"]\n }\n }\n}\n```\n\n## Common workflows\n\n### Create a worktree for a new feature\n\n```bash\ncd /path/to/repo\nwt create feature/my-branch\n```\n\n### Configure setup commands for a repo\n\n```bash\nwt config\n# Then add to the JSON:\n# "repo_overrides": {\n# "/path/to/repo": {\n# "setup_commands": ["npm install"]\n# }\n# }\n```\n\n### Browse all worktrees across repos\n\nRun `wt` from any directory outside a git repo to see worktrees from all registered repos.\n');
|
|
6
|
-
}
|
|
7
|
-
export {
|
|
8
|
-
printSkill
|
|
9
|
-
};
|