@cestoliv/wt 0.5.0-pr23.g58c078b → 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 -39
- package/SKILL.md +8 -31
- package/dist/{agent-D5XFA5Q6.js → agent-Z3YCY245.js} +6 -16
- package/dist/{chunk-OUWQ6NIV.js → chunk-FNAMNRUH.js} +1 -3
- package/dist/{chunk-BY3O2QUG.js → chunk-GHYUCETL.js} +15 -154
- package/dist/{chunk-AXP52BNV.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-ECNYOWOT.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-37JQF4PI.js +0 -271
- package/dist/list-2AC636OB.js +0 -19
- package/dist/prune-YYCUDNI5.js +0 -31
- package/dist/skill-OMSPELIY.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,26 +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 is patch-id based (via `git cherry`), so a single-commit branch
|
|
144
|
-
**squash-merged** through a PR is still detected. `wt prune` best-effort fetches
|
|
145
|
-
the remote first; if the base ref can't be resolved (offline, missing), it
|
|
146
|
-
removes nothing. The TUI exposes the same action under the `P` key. Works in
|
|
147
|
-
repo mode and across all registered repos in global mode (each against its own
|
|
148
|
-
`base_branch`).
|
|
149
|
-
|
|
150
124
|
## Configuration
|
|
151
125
|
|
|
152
126
|
Edit with `wt config` (`wt config --path` prints the file location —
|
|
@@ -160,10 +134,8 @@ Edit with `wt config` (`wt config --path` prints the file location —
|
|
|
160
134
|
| `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
|
|
161
135
|
| `setup_commands` | `[]` | Commands to run in new worktrees |
|
|
162
136
|
| `teardown_commands` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`) |
|
|
163
|
-
| `agent_command` | `"claude"`
|
|
164
|
-
| `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 |
|
|
165
138
|
| `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs and presses |
|
|
166
|
-
| `auto_refresh_minutes`| `5` | How often the interactive list re-fetches worktrees (shows a "last refreshed" header); `0` disables it |
|
|
167
139
|
| `repo_overrides` | `{}` | Per-repo overrides for any key above |
|
|
168
140
|
|
|
169
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,25 +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 is patch-id based (via `git cherry`), so a single-commit branch
|
|
115
|
-
that was **squash-merged** through a PR is still recognized as merged. It also
|
|
116
|
-
best-effort fetches the remote first so detection sees up-to-date refs; if the
|
|
117
|
-
base ref can't be resolved (e.g. offline), nothing is removed. Works in repo
|
|
118
|
-
mode (current repo) and global mode (all registered repos, each against its own
|
|
119
|
-
`base_branch`). The TUI exposes the same action under the `p` key.
|
|
120
|
-
|
|
121
100
|
### `wt config`
|
|
122
101
|
|
|
123
102
|
Open the global config file in `$EDITOR` (defaults to `nano`).
|
|
@@ -145,16 +124,14 @@ Config is stored as JSON. Get the path with `wt config --path`.
|
|
|
145
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 |
|
|
146
125
|
| `ide` | `string` | `"zed"` | IDE command to open worktrees with |
|
|
147
126
|
| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |
|
|
148
|
-
| `agent_command` | `string` | `"claude"`
|
|
149
|
-
| `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 |
|
|
150
128
|
| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |
|
|
151
|
-
| `auto_refresh_minutes`| `number` | `5` | How often the interactive list (`wt`) re-fetches worktrees and updates the "last refreshed" header; `0` disables auto-refresh |
|
|
152
129
|
| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |
|
|
153
130
|
| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |
|
|
154
131
|
|
|
155
132
|
### Per-repo overrides
|
|
156
133
|
|
|
157
|
-
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:
|
|
158
135
|
|
|
159
136
|
```json
|
|
160
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";
|
|
@@ -427,26 +427,16 @@ var VALID_MODES = [
|
|
|
427
427
|
var CLEANUP_DELAY_MS = 2e4;
|
|
428
428
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
429
429
|
async function createAgentWorktree(branch, planPrompt, options = {}) {
|
|
430
|
-
|
|
430
|
+
const mode = options.mode ?? "plan";
|
|
431
|
+
if (!VALID_MODES.includes(mode)) {
|
|
431
432
|
console.error(
|
|
432
|
-
pc.red(
|
|
433
|
-
`Invalid mode "${options.mode}". Valid modes: ${VALID_MODES.join(", ")}`
|
|
434
|
-
)
|
|
433
|
+
pc.red(`Invalid mode "${mode}". Valid modes: ${VALID_MODES.join(", ")}`)
|
|
435
434
|
);
|
|
436
435
|
process.exit(1);
|
|
437
436
|
}
|
|
438
437
|
const prepared = await prepareWorktree(branch, options);
|
|
439
438
|
if (!prepared) return;
|
|
440
439
|
const { status, config, worktreePath } = prepared;
|
|
441
|
-
const mode = options.mode ?? config.agent_mode ?? "default";
|
|
442
|
-
if (!VALID_MODES.includes(mode)) {
|
|
443
|
-
console.error(
|
|
444
|
-
pc.red(
|
|
445
|
-
`Invalid agent_mode "${mode}" in config. Valid modes: ${VALID_MODES.join(", ")}`
|
|
446
|
-
)
|
|
447
|
-
);
|
|
448
|
-
process.exit(1);
|
|
449
|
-
}
|
|
450
440
|
if (status === "exists") {
|
|
451
441
|
const prompt = options.existingWorktreePrompt ?? promptExistingWorktree;
|
|
452
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,19 +126,6 @@ function branchExists(repoRoot, branch) {
|
|
|
138
126
|
return false;
|
|
139
127
|
}
|
|
140
128
|
}
|
|
141
|
-
function isBranchMerged(repoRoot, branch, baseBranch) {
|
|
142
|
-
try {
|
|
143
|
-
const out = execFileSync("git", ["cherry", baseBranch, branch], {
|
|
144
|
-
cwd: repoRoot,
|
|
145
|
-
encoding: "utf8",
|
|
146
|
-
stdio: "pipe"
|
|
147
|
-
});
|
|
148
|
-
const lines = out.split("\n").filter((l) => l.trim().length > 0);
|
|
149
|
-
return lines.length > 0 && lines.every((l) => l.startsWith("-"));
|
|
150
|
-
} catch {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
129
|
function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
155
130
|
try {
|
|
156
131
|
execFileSync(
|
|
@@ -278,24 +253,8 @@ function shortenPath(p) {
|
|
|
278
253
|
const home = process.env.HOME ?? "";
|
|
279
254
|
return home && p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
280
255
|
}
|
|
281
|
-
function
|
|
282
|
-
const time = lastRefresh.toLocaleTimeString();
|
|
283
|
-
return `\u27F3 Last refreshed ${time} \xB7 every ${intervalMinutes}m`;
|
|
284
|
-
}
|
|
285
|
-
function reconcileSelectedIndex(items, prevPath, prevIndex) {
|
|
286
|
-
if (items.length === 0) return 0;
|
|
287
|
-
if (prevPath) {
|
|
288
|
-
const found = items.findIndex((w) => w.path === prevPath);
|
|
289
|
-
if (found !== -1) return found;
|
|
290
|
-
}
|
|
291
|
-
return Math.min(Math.max(0, prevIndex), items.length - 1);
|
|
292
|
-
}
|
|
293
|
-
function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null, intervalMinutes = 0) {
|
|
256
|
+
function buildListLayout(items, selectedIndex, query, mode) {
|
|
294
257
|
const header = [];
|
|
295
|
-
if (lastRefresh && intervalMinutes > 0) {
|
|
296
|
-
header.push(pc.dim(formatRefreshStatus(lastRefresh, intervalMinutes)));
|
|
297
|
-
header.push("");
|
|
298
|
-
}
|
|
299
258
|
if (mode === "global") {
|
|
300
259
|
header.push(
|
|
301
260
|
pc.dim("\u2139 Not in a git repository \u2014 showing all registered worktrees")
|
|
@@ -312,7 +271,7 @@ function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null,
|
|
|
312
271
|
for (const item of groupItems) {
|
|
313
272
|
const start = body.length;
|
|
314
273
|
const cursor = i === selectedIndex ? pc.cyan("\u25B6") : " ";
|
|
315
|
-
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) :
|
|
274
|
+
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) : pc.white(item.branch);
|
|
316
275
|
const pathLabel = pc.dim(shortenPath(item.path));
|
|
317
276
|
body.push(` ${cursor} ${branchLabel} ${pathLabel}`);
|
|
318
277
|
if (item.lastCommit) {
|
|
@@ -323,33 +282,28 @@ function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null,
|
|
|
323
282
|
}
|
|
324
283
|
}
|
|
325
284
|
const footer = [
|
|
326
|
-
pc.dim(
|
|
327
|
-
"\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 P prune \xB7 C create \xB7 A agent \xB7 Q quit"
|
|
328
|
-
)
|
|
285
|
+
pc.dim("\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 C create \xB7 A agent \xB7 Q quit")
|
|
329
286
|
];
|
|
330
287
|
return { header, body, footer, itemSpans };
|
|
331
288
|
}
|
|
332
|
-
function clampScroll(offset, span,
|
|
333
|
-
const maxOffset = Math.max(0, bodyLength -
|
|
289
|
+
function clampScroll(offset, span, viewportHeight, bodyLength) {
|
|
290
|
+
const maxOffset = Math.max(0, bodyLength - viewportHeight);
|
|
334
291
|
let next = offset;
|
|
335
292
|
if (span.start < next) next = span.start;
|
|
336
|
-
if (span.end >= next +
|
|
293
|
+
if (span.end >= next + viewportHeight) next = span.end - viewportHeight + 1;
|
|
337
294
|
return Math.max(0, Math.min(next, maxOffset));
|
|
338
295
|
}
|
|
339
|
-
function composeView(layout, offset,
|
|
296
|
+
function composeView(layout, offset, viewportHeight) {
|
|
340
297
|
const { header, body, footer } = layout;
|
|
341
|
-
const visible = body.slice(offset, offset +
|
|
342
|
-
while (visible.length <
|
|
298
|
+
const visible = body.slice(offset, offset + viewportHeight);
|
|
299
|
+
while (visible.length < viewportHeight) visible.push("");
|
|
343
300
|
const topSlot = offset > 0 ? pc.dim(" \u2191 more") : "";
|
|
344
|
-
const bottomSlot = offset +
|
|
301
|
+
const bottomSlot = offset + viewportHeight < body.length ? pc.dim(" \u2193 more") : "";
|
|
345
302
|
return [...header, topSlot, ...visible, bottomSlot, ...footer].join("\n");
|
|
346
303
|
}
|
|
347
304
|
function fixedHeight(layout) {
|
|
348
305
|
return layout.header.length + layout.footer.length + 2;
|
|
349
306
|
}
|
|
350
|
-
function viewportHeight(layout, rows) {
|
|
351
|
-
return Math.max(1, rows - fixedHeight(layout) - 1);
|
|
352
|
-
}
|
|
353
307
|
function setupRawMode() {
|
|
354
308
|
process.stdin.setRawMode(true);
|
|
355
309
|
process.stdin.resume();
|
|
@@ -360,17 +314,6 @@ function cleanupRawMode() {
|
|
|
360
314
|
process.stdin.pause();
|
|
361
315
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
362
316
|
}
|
|
363
|
-
function waitForKeypress() {
|
|
364
|
-
return new Promise((resolve) => {
|
|
365
|
-
process.stdin.setRawMode(true);
|
|
366
|
-
process.stdin.resume();
|
|
367
|
-
process.stdin.once("data", () => {
|
|
368
|
-
process.stdin.setRawMode(false);
|
|
369
|
-
process.stdin.pause();
|
|
370
|
-
resolve();
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
317
|
function renderRepoPicker(repos, selectedIndex, query) {
|
|
375
318
|
const lines = [];
|
|
376
319
|
lines.push(pc.dim("\u2139 Not in a git repository \u2014 select a repo to create in"));
|
|
@@ -519,25 +462,15 @@ async function runWizard(steps) {
|
|
|
519
462
|
}
|
|
520
463
|
return true;
|
|
521
464
|
}
|
|
522
|
-
async function runInteractiveList(allItems, mode, handlers
|
|
523
|
-
const { autoRefreshMinutes = 0 } = options;
|
|
524
|
-
const refreshEnabled = Number.isFinite(autoRefreshMinutes) && autoRefreshMinutes > 0;
|
|
465
|
+
async function runInteractiveList(allItems, mode, handlers) {
|
|
525
466
|
let query = "";
|
|
526
467
|
let selectedIndex = 0;
|
|
527
468
|
let scrollOffset = 0;
|
|
528
469
|
let filtered = allItems;
|
|
529
|
-
let lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : null;
|
|
530
470
|
const render = () => {
|
|
531
471
|
const rows = process.stdout.rows ?? 24;
|
|
532
|
-
const layout = buildListLayout(
|
|
533
|
-
|
|
534
|
-
selectedIndex,
|
|
535
|
-
query,
|
|
536
|
-
mode,
|
|
537
|
-
lastRefresh,
|
|
538
|
-
autoRefreshMinutes
|
|
539
|
-
);
|
|
540
|
-
const viewport = viewportHeight(layout, rows);
|
|
472
|
+
const layout = buildListLayout(filtered, selectedIndex, query, mode);
|
|
473
|
+
const viewport = Math.max(1, rows - fixedHeight(layout));
|
|
541
474
|
const span = layout.itemSpans[selectedIndex] ?? { start: 0, end: 0 };
|
|
542
475
|
scrollOffset = clampScroll(
|
|
543
476
|
scrollOffset,
|
|
@@ -552,9 +485,6 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
552
485
|
render();
|
|
553
486
|
return new Promise((resolve, reject) => {
|
|
554
487
|
let listenerActive = false;
|
|
555
|
-
let interacting = false;
|
|
556
|
-
let refreshing = false;
|
|
557
|
-
let refreshTimer = null;
|
|
558
488
|
const attachListener = () => {
|
|
559
489
|
if (!listenerActive) {
|
|
560
490
|
process.stdin.on("data", onData);
|
|
@@ -567,35 +497,9 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
567
497
|
process.stdout.removeListener("resize", render);
|
|
568
498
|
listenerActive = false;
|
|
569
499
|
};
|
|
570
|
-
const stopRefresh = () => {
|
|
571
|
-
if (refreshTimer !== null) {
|
|
572
|
-
clearInterval(refreshTimer);
|
|
573
|
-
refreshTimer = null;
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
const tick = async () => {
|
|
577
|
-
if (interacting || refreshing) return;
|
|
578
|
-
refreshing = true;
|
|
579
|
-
try {
|
|
580
|
-
const prevPath = filtered[selectedIndex]?.path;
|
|
581
|
-
allItems = await handlers.refreshItems();
|
|
582
|
-
filtered = filterItems(allItems, query);
|
|
583
|
-
selectedIndex = reconcileSelectedIndex(
|
|
584
|
-
filtered,
|
|
585
|
-
prevPath,
|
|
586
|
-
selectedIndex
|
|
587
|
-
);
|
|
588
|
-
lastRefresh = /* @__PURE__ */ new Date();
|
|
589
|
-
if (!interacting) render();
|
|
590
|
-
} catch {
|
|
591
|
-
} finally {
|
|
592
|
-
refreshing = false;
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
500
|
const onData = async (key) => {
|
|
596
501
|
try {
|
|
597
502
|
if (key === "" || key === "q" || key === "Q" || key === "\x1B") {
|
|
598
|
-
stopRefresh();
|
|
599
503
|
detachListener();
|
|
600
504
|
cleanupRawMode();
|
|
601
505
|
resolve();
|
|
@@ -608,7 +512,6 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
608
512
|
} else if (key === "\r") {
|
|
609
513
|
const item = filtered[selectedIndex];
|
|
610
514
|
if (item) {
|
|
611
|
-
stopRefresh();
|
|
612
515
|
detachListener();
|
|
613
516
|
cleanupRawMode();
|
|
614
517
|
handlers.onOpen(item);
|
|
@@ -617,21 +520,12 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
617
520
|
} else if (key === "d" || key === "D") {
|
|
618
521
|
const item = filtered[selectedIndex];
|
|
619
522
|
if (item) {
|
|
620
|
-
if (item.isMain) {
|
|
621
|
-
process.stdout.write(
|
|
622
|
-
pc.red(
|
|
623
|
-
"\nCannot delete the main repository \u2014 only worktrees can be removed.\n"
|
|
624
|
-
)
|
|
625
|
-
);
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
523
|
if (item.isCurrent) {
|
|
629
524
|
process.stdout.write(
|
|
630
525
|
pc.red("\nCannot delete the worktree you are currently in.\n")
|
|
631
526
|
);
|
|
632
527
|
return;
|
|
633
528
|
}
|
|
634
|
-
interacting = true;
|
|
635
529
|
detachListener();
|
|
636
530
|
cleanupRawMode();
|
|
637
531
|
const confirmed = await handlers.onDelete(item);
|
|
@@ -645,32 +539,9 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
645
539
|
);
|
|
646
540
|
setupRawMode();
|
|
647
541
|
attachListener();
|
|
648
|
-
interacting = false;
|
|
649
542
|
render();
|
|
650
543
|
}
|
|
651
|
-
} else if (key === "p" || key === "P") {
|
|
652
|
-
interacting = true;
|
|
653
|
-
detachListener();
|
|
654
|
-
cleanupRawMode();
|
|
655
|
-
const removed = await handlers.onWipe(allItems);
|
|
656
|
-
if (removed.length > 0) {
|
|
657
|
-
const removedSet = new Set(removed);
|
|
658
|
-
allItems = allItems.filter((w) => !removedSet.has(w));
|
|
659
|
-
filtered = filtered.filter((w) => !removedSet.has(w));
|
|
660
|
-
} else {
|
|
661
|
-
process.stdout.write(pc.dim("\nPress any key to continue\u2026"));
|
|
662
|
-
await waitForKeypress();
|
|
663
|
-
}
|
|
664
|
-
selectedIndex = Math.min(
|
|
665
|
-
selectedIndex,
|
|
666
|
-
Math.max(0, filtered.length - 1)
|
|
667
|
-
);
|
|
668
|
-
setupRawMode();
|
|
669
|
-
attachListener();
|
|
670
|
-
interacting = false;
|
|
671
|
-
render();
|
|
672
544
|
} else if (key === "c" || key === "C") {
|
|
673
|
-
interacting = true;
|
|
674
545
|
detachListener();
|
|
675
546
|
cleanupRawMode();
|
|
676
547
|
await handlers.onCreate();
|
|
@@ -680,13 +551,10 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
680
551
|
selectedIndex,
|
|
681
552
|
Math.max(0, filtered.length - 1)
|
|
682
553
|
);
|
|
683
|
-
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
684
554
|
setupRawMode();
|
|
685
555
|
attachListener();
|
|
686
|
-
interacting = false;
|
|
687
556
|
render();
|
|
688
557
|
} else if (key === "a" || key === "A") {
|
|
689
|
-
interacting = true;
|
|
690
558
|
detachListener();
|
|
691
559
|
cleanupRawMode();
|
|
692
560
|
await handlers.onAgent();
|
|
@@ -696,10 +564,8 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
696
564
|
selectedIndex,
|
|
697
565
|
Math.max(0, filtered.length - 1)
|
|
698
566
|
);
|
|
699
|
-
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
700
567
|
setupRawMode();
|
|
701
568
|
attachListener();
|
|
702
|
-
interacting = false;
|
|
703
569
|
render();
|
|
704
570
|
} else if (key === "\x7F") {
|
|
705
571
|
query = query.slice(0, -1);
|
|
@@ -715,16 +581,12 @@ async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
|
715
581
|
render();
|
|
716
582
|
}
|
|
717
583
|
} catch (err) {
|
|
718
|
-
stopRefresh();
|
|
719
584
|
detachListener();
|
|
720
585
|
cleanupRawMode();
|
|
721
586
|
reject(err);
|
|
722
587
|
}
|
|
723
588
|
};
|
|
724
589
|
attachListener();
|
|
725
|
-
if (refreshEnabled) {
|
|
726
|
-
refreshTimer = setInterval(tick, autoRefreshMinutes * 6e4);
|
|
727
|
-
}
|
|
728
590
|
});
|
|
729
591
|
}
|
|
730
592
|
|
|
@@ -735,7 +597,6 @@ export {
|
|
|
735
597
|
removeWorktree,
|
|
736
598
|
listWorktreeDirtyFiles,
|
|
737
599
|
branchExists,
|
|
738
|
-
isBranchMerged,
|
|
739
600
|
setUpstreamTracking,
|
|
740
601
|
fetchRemote,
|
|
741
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-YYCUDNI5.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-37JQF4PI.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-BY3O2QUG.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-ECNYOWOT.js");
|
|
100
|
-
await createWorktree(state.branch, { cwd: state.pickedRepo, store });
|
|
101
|
-
},
|
|
102
|
-
onAgent: async () => {
|
|
103
|
-
const { createAgentWorktree, VALID_MODES } = await import("./agent-D5XFA5Q6.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-2AC636OB.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-37JQF4PI.js";
|
|
10
|
-
import "./chunk-BY3O2QUG.js";
|
|
11
|
-
import "./chunk-OUWQ6NIV.js";
|
|
12
|
-
export {
|
|
13
|
-
buildMergedPredicate,
|
|
14
|
-
deleteWorktree,
|
|
15
|
-
prepareListItems,
|
|
16
|
-
runList,
|
|
17
|
-
selectWipeCandidates,
|
|
18
|
-
wipeWorktrees
|
|
19
|
-
};
|
package/dist/prune-YYCUDNI5.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
prepareListItems,
|
|
4
|
-
wipeWorktrees
|
|
5
|
-
} from "./chunk-37JQF4PI.js";
|
|
6
|
-
import "./chunk-BY3O2QUG.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-OMSPELIY.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 is patch-id based (via `git cherry`), so a single-commit branch\nthat was **squash-merged** through a PR is still recognized as merged. It also\nbest-effort fetches the remote first so detection sees up-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
|
-
};
|