@cestoliv/wt 0.5.0-pr24.g7ccdc37 → 0.5.1
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 +14 -6
- package/SKILL.md +11 -7
- package/dist/{agent-CSBQ4HUU.js → agent-QYE5UNA3.js} +2 -2
- package/dist/{chunk-63BGDDDM.js → chunk-BJDZXGOC.js} +3 -3
- package/dist/{chunk-S75RK5RZ.js → chunk-OA55NRNT.js} +1 -1
- package/dist/{chunk-47P7FKFX.js → chunk-XOP26UY4.js} +110 -26
- package/dist/cli.js +6 -6
- package/dist/{create-L3JURY7V.js → create-K4OQIX7A.js} +2 -2
- package/dist/{list-TTNX2HBM.js → list-GLLMKIKE.js} +2 -2
- package/dist/{prune-NAGNNGQ5.js → prune-HKCDPXQD.js} +2 -2
- package/dist/{skill-GZVGKD5Z.js → skill-T5VOI4ZB.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,7 +16,10 @@ prompt.
|
|
|
16
16
|
npm install -g @cestoliv/wt # also the update command
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Requires Node.js 20+ and Git. The command is `wt`.
|
|
19
|
+
Requires Node.js 20+ and Git. The command is `wt`. Optionally install the
|
|
20
|
+
[`gh`](https://cli.github.com/) and/or [`glab`](https://gitlab.com/gitlab-org/cli)
|
|
21
|
+
CLIs (authenticated) to let `wt prune` confirm merges via merged PRs/MRs — see
|
|
22
|
+
[Prune](#prune--wt-prune).
|
|
20
23
|
|
|
21
24
|
## Let your AI assistant set it up
|
|
22
25
|
|
|
@@ -140,11 +143,16 @@ and force-confirming when git refuses (submodules or uncommitted changes), just
|
|
|
140
143
|
like a manual `D` delete. The branch itself stays; only the worktree is removed.
|
|
141
144
|
Your `teardown_commands` run before each removal.
|
|
142
145
|
|
|
143
|
-
Merge detection
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
Merge detection is tiered. Patch-id matches (via `git cherry`) catch a
|
|
147
|
+
single-commit branch **squash-merged** through a PR, offline. For the ambiguous
|
|
148
|
+
case — the branch tip is an ancestor of base but 0 commits ahead, which both a
|
|
149
|
+
**fast-forward / merge-commit** merge and a worktree holding only *uncommitted*
|
|
150
|
+
work produce — it consults the **forge**: a merged PR/MR (via `gh` for GitHub or
|
|
151
|
+
`glab` for GitLab, including self-hosted, auto-detected from the remote) is the
|
|
152
|
+
only reliable signal, so a branch with unmerged work-in-progress is never
|
|
153
|
+
mistaken for merged. If no forge CLI is available (or you're offline) such
|
|
154
|
+
branches are simply left alone. A worktree still sitting exactly on the base
|
|
155
|
+
commit is never offered. `wt prune` best-effort fetches
|
|
148
156
|
the remote first; if the base ref can't be resolved (offline, missing), it
|
|
149
157
|
removes nothing. The TUI exposes the same action under the `P` key. Works in
|
|
150
158
|
repo mode and across all registered repos in global mode (each against its own
|
package/SKILL.md
CHANGED
|
@@ -111,13 +111,17 @@ only the worktree is removed.
|
|
|
111
111
|
wt prune # review and remove merged worktrees, one prompt per branch
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
-
Merge detection
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
Merge detection works in tiers. A branch whose diff already exists in base by
|
|
115
|
+
patch id (via `git cherry`, so a single-commit branch **squash-merged** through a
|
|
116
|
+
PR is recognized offline) is merged. For the ambiguous case — the branch tip is
|
|
117
|
+
an ancestor of base but 0 commits ahead, which a **fast-forward / merge-commit**
|
|
118
|
+
merge and a worktree holding only *uncommitted* work both produce — it consults
|
|
119
|
+
the **forge**: a merged PR/MR (via `gh` for GitHub, `glab` for GitLab incl.
|
|
120
|
+
self-hosted, auto-detected from the remote) is the only reliable signal. If the
|
|
121
|
+
forge can't answer (CLI missing, offline, branch unpushed, no merged PR/MR) the
|
|
122
|
+
branch is left alone. A worktree still sitting exactly on the base commit is
|
|
123
|
+
never offered. `wt prune` also best-effort fetches the remote first so detection
|
|
124
|
+
sees up-to-date refs; if the
|
|
121
125
|
base ref can't be resolved (e.g. offline), nothing is removed. Works in repo
|
|
122
126
|
mode (current repo) and global mode (all registered repos, each against its own
|
|
123
127
|
`base_branch`). The TUI exposes the same action under the `p` key.
|
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
openConfiguredIde,
|
|
4
4
|
prepareWorktree,
|
|
5
5
|
promptExistingWorktree
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-OA55NRNT.js";
|
|
7
|
+
import "./chunk-XOP26UY4.js";
|
|
8
8
|
import "./chunk-OUWQ6NIV.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/agent.ts
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
runInteractiveList,
|
|
15
15
|
runRepoPicker,
|
|
16
16
|
runWizard
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-XOP26UY4.js";
|
|
18
18
|
import {
|
|
19
19
|
createStore,
|
|
20
20
|
getEffectiveConfig,
|
|
@@ -96,11 +96,11 @@ async function runList(options = {}) {
|
|
|
96
96
|
if (!await runWizard(steps)) return;
|
|
97
97
|
if (state.pickedRepo === void 0 || state.branch === void 0)
|
|
98
98
|
return;
|
|
99
|
-
const { createWorktree } = await import("./create-
|
|
99
|
+
const { createWorktree } = await import("./create-K4OQIX7A.js");
|
|
100
100
|
await createWorktree(state.branch, { cwd: state.pickedRepo, store });
|
|
101
101
|
},
|
|
102
102
|
onAgent: async () => {
|
|
103
|
-
const { createAgentWorktree, VALID_MODES } = await import("./agent-
|
|
103
|
+
const { createAgentWorktree, VALID_MODES } = await import("./agent-QYE5UNA3.js");
|
|
104
104
|
const state = {
|
|
105
105
|
pickedRepo: repoRoot ?? void 0
|
|
106
106
|
};
|
|
@@ -5,13 +5,85 @@ import {
|
|
|
5
5
|
} from "./chunk-OUWQ6NIV.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/git.ts
|
|
8
|
-
import { execFileSync } from "child_process";
|
|
8
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
9
9
|
import { existsSync, realpathSync, rmSync } from "fs";
|
|
10
10
|
import path from "path";
|
|
11
|
+
|
|
12
|
+
// src/lib/forge.ts
|
|
13
|
+
import { execFileSync } from "child_process";
|
|
14
|
+
function parseRemoteHost(url) {
|
|
15
|
+
const u = url.trim();
|
|
16
|
+
if (!u) return null;
|
|
17
|
+
const scp = u.match(/^(?:[^@/]+@)?([^:/]+):(?!\/\/)/);
|
|
18
|
+
if (scp) return scp[1].toLowerCase();
|
|
19
|
+
const schemed = u.match(/^[a-z][a-z0-9+.-]*:\/\/(?:[^@/]+@)?([^:/]+)/i);
|
|
20
|
+
if (schemed) return schemed[1].toLowerCase();
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
function selectForgeTool(host) {
|
|
24
|
+
if (!host) return null;
|
|
25
|
+
if (host.startsWith("github.") || host.endsWith(".github.com")) return "gh";
|
|
26
|
+
return "glab";
|
|
27
|
+
}
|
|
28
|
+
function buildMergedQuery(tool, branch) {
|
|
29
|
+
if (tool === "gh") {
|
|
30
|
+
return [
|
|
31
|
+
"pr",
|
|
32
|
+
"list",
|
|
33
|
+
"--head",
|
|
34
|
+
branch,
|
|
35
|
+
"--state",
|
|
36
|
+
"merged",
|
|
37
|
+
"--json",
|
|
38
|
+
"number"
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return ["mr", "list", "--merged", "--source-branch", branch, "-F", "json"];
|
|
42
|
+
}
|
|
43
|
+
function parseMergedResult(stdout) {
|
|
44
|
+
try {
|
|
45
|
+
const data = JSON.parse(stdout);
|
|
46
|
+
return Array.isArray(data) && data.length > 0;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var defaultRunner = {
|
|
52
|
+
remoteUrl(repoRoot, remote) {
|
|
53
|
+
return execFileSync("git", ["remote", "get-url", remote], {
|
|
54
|
+
cwd: repoRoot,
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
stdio: "pipe"
|
|
57
|
+
}).trim();
|
|
58
|
+
},
|
|
59
|
+
query(repoRoot, tool, args) {
|
|
60
|
+
return execFileSync(tool, args, {
|
|
61
|
+
cwd: repoRoot,
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
stdio: "pipe",
|
|
64
|
+
timeout: 15e3
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
function hasMergedPullRequest(repoRoot, branch, remote = "origin", runner = defaultRunner) {
|
|
69
|
+
try {
|
|
70
|
+
const tool = selectForgeTool(
|
|
71
|
+
parseRemoteHost(runner.remoteUrl(repoRoot, remote))
|
|
72
|
+
);
|
|
73
|
+
if (!tool) return false;
|
|
74
|
+
return parseMergedResult(
|
|
75
|
+
runner.query(repoRoot, tool, buildMergedQuery(tool, branch))
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/lib/git.ts
|
|
11
83
|
function getRepoRoot(cwd = process.cwd()) {
|
|
12
84
|
try {
|
|
13
85
|
const realCwd = realpathSync(cwd);
|
|
14
|
-
const output =
|
|
86
|
+
const output = execFileSync2("git", ["worktree", "list", "--porcelain"], {
|
|
15
87
|
cwd: realCwd,
|
|
16
88
|
encoding: "utf8",
|
|
17
89
|
stdio: "pipe"
|
|
@@ -25,7 +97,7 @@ function getRepoRoot(cwd = process.cwd()) {
|
|
|
25
97
|
function listWorktrees(repoRoot, cwd = process.cwd()) {
|
|
26
98
|
const realRepoRoot = realpathSync(repoRoot);
|
|
27
99
|
const realCwd = realpathSync(cwd);
|
|
28
|
-
const output =
|
|
100
|
+
const output = execFileSync2("git", ["worktree", "list", "--porcelain"], {
|
|
29
101
|
cwd: realRepoRoot,
|
|
30
102
|
encoding: "utf8"
|
|
31
103
|
});
|
|
@@ -35,7 +107,7 @@ function listWorktrees(repoRoot, cwd = process.cwd()) {
|
|
|
35
107
|
).join('; echo "---SEP---"; ');
|
|
36
108
|
let commits = [];
|
|
37
109
|
try {
|
|
38
|
-
const batchOutput =
|
|
110
|
+
const batchOutput = execFileSync2("sh", ["-c", script], {
|
|
39
111
|
encoding: "utf8",
|
|
40
112
|
timeout: 8e3
|
|
41
113
|
});
|
|
@@ -65,7 +137,7 @@ function parseWorktreeList(output, repoRoot, cwd) {
|
|
|
65
137
|
}
|
|
66
138
|
function addWorktree(repoRoot, worktreePath, branch, baseBranch) {
|
|
67
139
|
if (baseBranch) {
|
|
68
|
-
|
|
140
|
+
execFileSync2(
|
|
69
141
|
"git",
|
|
70
142
|
["worktree", "add", "-b", branch, worktreePath, baseBranch],
|
|
71
143
|
{
|
|
@@ -73,7 +145,7 @@ function addWorktree(repoRoot, worktreePath, branch, baseBranch) {
|
|
|
73
145
|
}
|
|
74
146
|
);
|
|
75
147
|
} else {
|
|
76
|
-
|
|
148
|
+
execFileSync2("git", ["worktree", "add", worktreePath, branch], {
|
|
77
149
|
cwd: repoRoot
|
|
78
150
|
});
|
|
79
151
|
}
|
|
@@ -90,7 +162,7 @@ function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
|
90
162
|
throw new Error("Refusing to remove the main worktree");
|
|
91
163
|
}
|
|
92
164
|
try {
|
|
93
|
-
|
|
165
|
+
execFileSync2(
|
|
94
166
|
"git",
|
|
95
167
|
["worktree", "remove", ...force ? ["--force"] : [], worktreePath],
|
|
96
168
|
{ cwd: repoRoot, stdio: "pipe" }
|
|
@@ -100,7 +172,7 @@ function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
|
100
172
|
if (existsSync(worktreePath)) {
|
|
101
173
|
rmSync(worktreePath, { recursive: true, force: true });
|
|
102
174
|
}
|
|
103
|
-
|
|
175
|
+
execFileSync2("git", ["worktree", "prune"], {
|
|
104
176
|
cwd: repoRoot,
|
|
105
177
|
stdio: "pipe"
|
|
106
178
|
});
|
|
@@ -108,7 +180,7 @@ function removeWorktree(repoRoot, worktreePath, force = false) {
|
|
|
108
180
|
}
|
|
109
181
|
function listWorktreeDirtyFiles(worktreePath) {
|
|
110
182
|
try {
|
|
111
|
-
const out =
|
|
183
|
+
const out = execFileSync2("git", ["status", "--short"], {
|
|
112
184
|
cwd: worktreePath,
|
|
113
185
|
encoding: "utf8"
|
|
114
186
|
});
|
|
@@ -119,12 +191,12 @@ function listWorktreeDirtyFiles(worktreePath) {
|
|
|
119
191
|
}
|
|
120
192
|
function branchExists(repoRoot, branch) {
|
|
121
193
|
try {
|
|
122
|
-
const local =
|
|
194
|
+
const local = execFileSync2("git", ["branch", "--list", branch], {
|
|
123
195
|
cwd: repoRoot,
|
|
124
196
|
encoding: "utf8"
|
|
125
197
|
}).trim();
|
|
126
198
|
if (local) return true;
|
|
127
|
-
const remote =
|
|
199
|
+
const remote = execFileSync2(
|
|
128
200
|
"git",
|
|
129
201
|
["ls-remote", "--heads", "origin", branch],
|
|
130
202
|
{
|
|
@@ -140,7 +212,7 @@ function branchExists(repoRoot, branch) {
|
|
|
140
212
|
}
|
|
141
213
|
function isAncestor(repoRoot, ancestor, descendant) {
|
|
142
214
|
try {
|
|
143
|
-
|
|
215
|
+
execFileSync2("git", ["merge-base", "--is-ancestor", ancestor, descendant], {
|
|
144
216
|
cwd: repoRoot,
|
|
145
217
|
stdio: "pipe"
|
|
146
218
|
});
|
|
@@ -149,32 +221,44 @@ function isAncestor(repoRoot, ancestor, descendant) {
|
|
|
149
221
|
return false;
|
|
150
222
|
}
|
|
151
223
|
}
|
|
152
|
-
function
|
|
224
|
+
function hasRemoteTrackingRef(repoRoot, remote, branch) {
|
|
225
|
+
try {
|
|
226
|
+
execFileSync2(
|
|
227
|
+
"git",
|
|
228
|
+
["rev-parse", "--verify", "--quiet", `refs/remotes/${remote}/${branch}`],
|
|
229
|
+
{ cwd: repoRoot, stdio: "pipe" }
|
|
230
|
+
);
|
|
231
|
+
return true;
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function isBranchMerged(repoRoot, branch, baseBranch, forgeCheck = hasMergedPullRequest) {
|
|
153
237
|
try {
|
|
154
|
-
const
|
|
238
|
+
const out = execFileSync2("git", ["cherry", baseBranch, branch], {
|
|
155
239
|
cwd: repoRoot,
|
|
156
240
|
encoding: "utf8",
|
|
157
241
|
stdio: "pipe"
|
|
158
|
-
})
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
const out = execFileSync("git", ["cherry", baseBranch, branch], {
|
|
242
|
+
});
|
|
243
|
+
const lines = out.split("\n").filter((l) => l.trim().length > 0);
|
|
244
|
+
if (lines.length > 0 && lines.every((l) => l.startsWith("-"))) return true;
|
|
245
|
+
const revParse = (ref) => execFileSync2("git", ["rev-parse", ref], {
|
|
165
246
|
cwd: repoRoot,
|
|
166
247
|
encoding: "utf8",
|
|
167
248
|
stdio: "pipe"
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
|
|
249
|
+
}).trim();
|
|
250
|
+
if (revParse(branch) === revParse(baseBranch)) return false;
|
|
251
|
+
if (!isAncestor(repoRoot, branch, baseBranch)) return false;
|
|
252
|
+
const remote = baseBranch.includes("/") ? baseBranch.split("/", 1)[0] : "origin";
|
|
253
|
+
if (!hasRemoteTrackingRef(repoRoot, remote, branch)) return false;
|
|
254
|
+
return forgeCheck(repoRoot, branch, remote);
|
|
171
255
|
} catch {
|
|
172
256
|
return false;
|
|
173
257
|
}
|
|
174
258
|
}
|
|
175
259
|
function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
176
260
|
try {
|
|
177
|
-
|
|
261
|
+
execFileSync2(
|
|
178
262
|
"git",
|
|
179
263
|
["branch", "--set-upstream-to", `${remote}/${branch}`, branch],
|
|
180
264
|
{ cwd: worktreePath, stdio: "pipe" }
|
|
@@ -183,7 +267,7 @@ function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
|
183
267
|
}
|
|
184
268
|
}
|
|
185
269
|
function fetchRemote(repoRoot, remote = "origin") {
|
|
186
|
-
|
|
270
|
+
execFileSync2("git", ["fetch", remote], {
|
|
187
271
|
cwd: repoRoot,
|
|
188
272
|
stdio: "pipe",
|
|
189
273
|
timeout: 3e4
|
package/dist/cli.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
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.
|
|
7
|
-
const { runList } = await import("./list-
|
|
6
|
+
program.name("wt").description("Git worktree manager").version("0.5.1").action(async () => {
|
|
7
|
+
const { runList } = await import("./list-GLLMKIKE.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-K4OQIX7A.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(
|
|
@@ -16,14 +16,14 @@ program.command("agent <branch> <plan_prompt>").description("Create a worktree a
|
|
|
16
16
|
"Claude Code permission mode (default, plan, auto, etc.); overrides the configured agent_mode"
|
|
17
17
|
).action(
|
|
18
18
|
async (branch, planPrompt, options) => {
|
|
19
|
-
const { createAgentWorktree } = await import("./agent-
|
|
19
|
+
const { createAgentWorktree } = await import("./agent-QYE5UNA3.js");
|
|
20
20
|
await createAgentWorktree(branch, planPrompt, { mode: options.mode });
|
|
21
21
|
}
|
|
22
22
|
);
|
|
23
23
|
program.command("prune").description(
|
|
24
24
|
"Remove worktrees whose branch has been merged into the base branch"
|
|
25
25
|
).action(async () => {
|
|
26
|
-
const { runPrune } = await import("./prune-
|
|
26
|
+
const { runPrune } = await import("./prune-HKCDPXQD.js");
|
|
27
27
|
await runPrune();
|
|
28
28
|
});
|
|
29
29
|
program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
|
|
@@ -36,7 +36,7 @@ program.command("config").description("Open the config file in $EDITOR").option(
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
program.command("skill").description("Print the wt skill file to stdout").action(async () => {
|
|
39
|
-
const { printSkill } = await import("./skill-
|
|
39
|
+
const { printSkill } = await import("./skill-T5VOI4ZB.js");
|
|
40
40
|
printSkill();
|
|
41
41
|
});
|
|
42
42
|
await program.parseAsync(process.argv);
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
openConfiguredIde,
|
|
5
5
|
prepareWorktree,
|
|
6
6
|
promptExistingWorktree
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-OA55NRNT.js";
|
|
8
|
+
import "./chunk-XOP26UY4.js";
|
|
9
9
|
import "./chunk-OUWQ6NIV.js";
|
|
10
10
|
export {
|
|
11
11
|
createWorktree,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/commands/skill.ts
|
|
4
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');
|
|
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 works in tiers. A branch whose diff already exists in base by\npatch id (via `git cherry`, so a single-commit branch **squash-merged** through a\nPR is recognized offline) is merged. For the ambiguous case \u2014 the branch tip is\nan ancestor of base but 0 commits ahead, which a **fast-forward / merge-commit**\nmerge and a worktree holding only *uncommitted* work both produce \u2014 it consults\nthe **forge**: a merged PR/MR (via `gh` for GitHub, `glab` for GitLab incl.\nself-hosted, auto-detected from the remote) is the only reliable signal. If the\nforge can\'t answer (CLI missing, offline, branch unpushed, no merged PR/MR) the\nbranch is left alone. A worktree still sitting exactly on the base commit is\nnever offered. `wt prune` also best-effort fetches the remote first so detection\nsees 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
6
|
}
|
|
7
7
|
export {
|
|
8
8
|
printSkill
|