@cestoliv/wt 0.4.1 → 0.5.0-pr20.gd7a2b6f
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 +36 -3
- package/SKILL.md +47 -7
- package/dist/{agent-CIGIZGIS.js → agent-NMJZAZGW.js} +4 -3
- package/dist/{chunk-BSMGKL27.js → chunk-EVCULRZW.js} +2 -2
- package/dist/chunk-NDDDPDNZ.js +271 -0
- package/dist/{chunk-XM4EABZV.js → chunk-O5GOGQUU.js} +172 -27
- package/dist/{chunk-FNAMNRUH.js → chunk-QVYSEMSL.js} +1 -0
- package/dist/cli.js +13 -7
- package/dist/{config-DBM7HJE4.js → config-QLWPW2UZ.js} +2 -1
- package/dist/{create-WRMXQIGN.js → create-CB5I7ZRF.js} +3 -3
- package/dist/list-Z7A74ARA.js +19 -0
- package/dist/prune-XSBBNQQY.js +31 -0
- package/dist/skill-DJ2CRU4O.js +9 -0
- package/package.json +1 -1
- package/dist/list-WC5GM3PI.js +0 -137
- package/dist/skill-FEUVQMK6.js +0 -9
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ 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
40
|
wt agent fix-bug "Fix bug" --mode auto # Use auto mode instead of plan
|
|
41
|
+
wt prune # Remove merged worktrees (per-branch confirm)
|
|
41
42
|
wt config # Edit config in $EDITOR
|
|
42
43
|
wt skill # Print the skill file (for AI agents)
|
|
43
44
|
```
|
|
@@ -90,12 +91,23 @@ MY-PROJECT
|
|
|
90
91
|
feat/dashboard ~/dev/my-project-feat-dashboard
|
|
91
92
|
wip: add chart component (1d ago)
|
|
92
93
|
|
|
93
|
-
↕ navigate · Enter open · D delete · C create · Q quit
|
|
94
|
+
↕ navigate · Enter open · D delete · P prune · C create · A agent · Q quit
|
|
94
95
|
```
|
|
95
96
|
|
|
96
97
|
Type to fuzzy-filter branches instantly. Inside a repo it shows that repo's
|
|
97
|
-
worktrees
|
|
98
|
-
|
|
98
|
+
worktrees; run it outside any repo ("home") to browse worktrees across all
|
|
99
|
+
registered repos.
|
|
100
|
+
|
|
101
|
+
`C` creates a worktree and `A` creates one and starts an AI agent in it — both
|
|
102
|
+
work from anywhere and are step-by-step wizards. Run from home and they first
|
|
103
|
+
prompt for the repo, then the branch (`C` stops there); `A` adds a plan prompt
|
|
104
|
+
and a permission mode — **worktree (repo → branch) → plan prompt → permission
|
|
105
|
+
mode**. Pressing `Esc` steps back to the previous question (answers preserved),
|
|
106
|
+
or back to the list from the first step. After creating, the list **refreshes
|
|
107
|
+
and stays open** (preserving your search and cursor) instead of exiting — only
|
|
108
|
+
`Enter` and `Q`/`Esc` leave the TUI. `P` prunes every worktree whose branch has
|
|
109
|
+
already been merged (see [`wt prune`](#prune--wt-prune) below). Note that
|
|
110
|
+
`a`/`c`/`d`/`p` are command keys, so they can't be typed into the search box.
|
|
99
111
|
|
|
100
112
|
## Create — `wt create [branch]`
|
|
101
113
|
|
|
@@ -111,6 +123,26 @@ registered repos.
|
|
|
111
123
|
If the path already exists, `wt create` offers to open it in your IDE instead of
|
|
112
124
|
erroring (in a non-interactive shell it exits non-zero).
|
|
113
125
|
|
|
126
|
+
## Prune — `wt prune`
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
wt prune # remove every merged worktree, one confirmation per branch
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Cleans up the worktrees you're done with: it finds every worktree whose branch
|
|
133
|
+
has already been merged into the base branch (`base_branch`, default
|
|
134
|
+
`origin/main`) and removes it — **always confirming each branch individually**,
|
|
135
|
+
and force-confirming when git refuses (submodules or uncommitted changes), just
|
|
136
|
+
like a manual `D` delete. The branch itself stays; only the worktree is removed.
|
|
137
|
+
Your `teardown_commands` run before each removal.
|
|
138
|
+
|
|
139
|
+
Merge detection is patch-id based (via `git cherry`), so a single-commit branch
|
|
140
|
+
**squash-merged** through a PR is still detected. `wt prune` best-effort fetches
|
|
141
|
+
the remote first; if the base ref can't be resolved (offline, missing), it
|
|
142
|
+
removes nothing. The TUI exposes the same action under the `P` key. Works in
|
|
143
|
+
repo mode and across all registered repos in global mode (each against its own
|
|
144
|
+
`base_branch`).
|
|
145
|
+
|
|
114
146
|
## Configuration
|
|
115
147
|
|
|
116
148
|
Edit with `wt config` (`wt config --path` prints the file location —
|
|
@@ -126,6 +158,7 @@ Edit with `wt config` (`wt config --path` prints the file location —
|
|
|
126
158
|
| `teardown_commands` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`) |
|
|
127
159
|
| `agent_command` | `"claude --permission-mode plan"` | Base command; `--permission-mode` replaced by `--mode` option, then prompt appended |
|
|
128
160
|
| `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs and presses |
|
|
161
|
+
| `auto_refresh_minutes`| `5` | How often the interactive list re-fetches worktrees (shows a "last refreshed" header); `0` disables it |
|
|
129
162
|
| `repo_overrides` | `{}` | Per-repo overrides for any key above |
|
|
130
163
|
|
|
131
164
|
Override any key per repo:
|
package/SKILL.md
CHANGED
|
@@ -15,12 +15,32 @@ Launch the interactive TUI. Shows worktrees for the current repo (repo mode) or
|
|
|
15
15
|
|
|
16
16
|
**Keybindings in the TUI:**
|
|
17
17
|
|
|
18
|
-
- Arrow keys
|
|
19
|
-
- `Enter` — open worktree in IDE
|
|
20
|
-
- `
|
|
21
|
-
- `
|
|
22
|
-
-
|
|
23
|
-
- `
|
|
18
|
+
- Arrow keys — navigate
|
|
19
|
+
- `Enter` — open worktree in IDE (exits the TUI)
|
|
20
|
+
- `D` — delete worktree
|
|
21
|
+
- `P` — prune all merged worktrees (per-branch confirmation)
|
|
22
|
+
- `C` — create a new worktree (works in both repo and global mode)
|
|
23
|
+
- `A` — create a worktree and start an AI agent in it (works in both modes)
|
|
24
|
+
- type to search · `Backspace` — edit search
|
|
25
|
+
- `Q` / `Esc` — quit
|
|
26
|
+
|
|
27
|
+
`C` and `A` are step-by-step wizards. In global mode (run from outside a repo /
|
|
28
|
+
"home") they start by prompting for the repo (picker), then the branch; in repo
|
|
29
|
+
mode the repo is fixed so they start at the branch. `A` then adds two more
|
|
30
|
+
steps:
|
|
31
|
+
|
|
32
|
+
- `C` — **worktree (repo → branch)**
|
|
33
|
+
- `A` — **worktree (repo → branch) → plan prompt → permission mode**
|
|
34
|
+
|
|
35
|
+
Pressing `Esc` at any step goes back to the previous step (your earlier answers
|
|
36
|
+
are preserved); pressing `Esc` on the first step returns to the list.
|
|
37
|
+
|
|
38
|
+
After a create or agent action the TUI **refreshes and stays open** on the list
|
|
39
|
+
(your search and cursor are preserved) rather than exiting — only `Enter` (open)
|
|
40
|
+
and `Q`/`Esc` exit.
|
|
41
|
+
|
|
42
|
+
Because `a`/`A`, `c`/`C`, `d`/`D`, and `p`/`P` are reserved as command keys,
|
|
43
|
+
those letters can't be typed into the search box.
|
|
24
44
|
|
|
25
45
|
### `wt create [branch]`
|
|
26
46
|
|
|
@@ -78,6 +98,25 @@ If the worktree path already exists, `wt agent` prompts you to **open it in the
|
|
|
78
98
|
IDE**, **open it and start the agent**, or **quit** — instead of erroring. (In a
|
|
79
99
|
non-interactive shell it errors with a non-zero exit instead of prompting.)
|
|
80
100
|
|
|
101
|
+
### `wt prune`
|
|
102
|
+
|
|
103
|
+
Remove every worktree whose branch has already been merged into the base
|
|
104
|
+
branch (`base_branch`, default `origin/main`). Each candidate is confirmed
|
|
105
|
+
individually — and force-confirmed when git refuses (submodules / uncommitted
|
|
106
|
+
changes), exactly like a manual `d` delete. The branch itself is left intact;
|
|
107
|
+
only the worktree is removed.
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
wt prune # review and remove merged worktrees, one prompt per branch
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Merge detection is patch-id based (via `git cherry`), so a single-commit branch
|
|
114
|
+
that was **squash-merged** through a PR is still recognized as merged. It also
|
|
115
|
+
best-effort fetches the remote first so detection sees up-to-date refs; if the
|
|
116
|
+
base ref can't be resolved (e.g. offline), nothing is removed. Works in repo
|
|
117
|
+
mode (current repo) and global mode (all registered repos, each against its own
|
|
118
|
+
`base_branch`). The TUI exposes the same action under the `p` key.
|
|
119
|
+
|
|
81
120
|
### `wt config`
|
|
82
121
|
|
|
83
122
|
Open the global config file in `$EDITOR` (defaults to `nano`).
|
|
@@ -107,12 +146,13 @@ Config is stored as JSON. Get the path with `wt config --path`.
|
|
|
107
146
|
| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |
|
|
108
147
|
| `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 |
|
|
109
148
|
| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |
|
|
149
|
+
| `auto_refresh_minutes`| `number` | `5` | How often the interactive list (`wt`) re-fetches worktrees and updates the "last refreshed" header; `0` disables auto-refresh |
|
|
110
150
|
| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |
|
|
111
151
|
| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |
|
|
112
152
|
|
|
113
153
|
### Per-repo overrides
|
|
114
154
|
|
|
115
|
-
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:
|
|
155
|
+
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`, `auto_refresh_minutes`) for a specific repo:
|
|
116
156
|
|
|
117
157
|
```json
|
|
118
158
|
{
|
|
@@ -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-EVCULRZW.js";
|
|
7
|
+
import "./chunk-O5GOGQUU.js";
|
|
8
|
+
import "./chunk-QVYSEMSL.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/agent.ts
|
|
11
11
|
import * as clack from "@clack/prompts";
|
|
@@ -538,5 +538,6 @@ function reportTriggerFailure(result, chord) {
|
|
|
538
538
|
);
|
|
539
539
|
}
|
|
540
540
|
export {
|
|
541
|
+
VALID_MODES,
|
|
541
542
|
createAgentWorktree
|
|
542
543
|
};
|
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
runCommands,
|
|
14
14
|
runRepoPicker,
|
|
15
15
|
setUpstreamTracking
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-O5GOGQUU.js";
|
|
17
17
|
import {
|
|
18
18
|
createStore,
|
|
19
19
|
getEffectiveConfig
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-QVYSEMSL.js";
|
|
21
21
|
|
|
22
22
|
// src/commands/create.ts
|
|
23
23
|
import { existsSync } from "fs";
|
|
@@ -0,0 +1,271 @@
|
|
|
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-O5GOGQUU.js";
|
|
18
|
+
import {
|
|
19
|
+
createStore,
|
|
20
|
+
getEffectiveConfig,
|
|
21
|
+
getGlobalConfig
|
|
22
|
+
} from "./chunk-QVYSEMSL.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-CB5I7ZRF.js");
|
|
100
|
+
await createWorktree(state.branch, { cwd: state.pickedRepo, store });
|
|
101
|
+
},
|
|
102
|
+
onAgent: async () => {
|
|
103
|
+
const { createAgentWorktree, VALID_MODES } = await import("./agent-NMJZAZGW.js");
|
|
104
|
+
const state = {
|
|
105
|
+
pickedRepo: repoRoot ?? void 0,
|
|
106
|
+
mode: "plan"
|
|
107
|
+
};
|
|
108
|
+
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
109
|
+
steps.push(async () => {
|
|
110
|
+
const entered = await clack.text({
|
|
111
|
+
message: "Plan prompt for the agent:",
|
|
112
|
+
initialValue: state.plan,
|
|
113
|
+
validate: (v) => !v || v.length === 0 ? "Required" : void 0
|
|
114
|
+
});
|
|
115
|
+
if (clack.isCancel(entered)) return false;
|
|
116
|
+
state.plan = entered;
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
steps.push(async () => {
|
|
120
|
+
const chosen = await clack.select({
|
|
121
|
+
message: "Permission mode:",
|
|
122
|
+
initialValue: state.mode,
|
|
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.path !== wt.repoRoot && 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
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createStore,
|
|
4
4
|
getGlobalConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QVYSEMSL.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/git.ts
|
|
8
8
|
import { execFileSync } from "child_process";
|
|
@@ -126,6 +126,19 @@ function branchExists(repoRoot, branch) {
|
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
function isBranchMerged(repoRoot, branch, baseBranch) {
|
|
130
|
+
try {
|
|
131
|
+
const out = execFileSync("git", ["cherry", baseBranch, branch], {
|
|
132
|
+
cwd: repoRoot,
|
|
133
|
+
encoding: "utf8",
|
|
134
|
+
stdio: "pipe"
|
|
135
|
+
});
|
|
136
|
+
const lines = out.split("\n").filter((l) => l.trim().length > 0);
|
|
137
|
+
return lines.length > 0 && lines.every((l) => l.startsWith("-"));
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
129
142
|
function setUpstreamTracking(worktreePath, branch, remote = "origin") {
|
|
130
143
|
try {
|
|
131
144
|
execFileSync(
|
|
@@ -253,8 +266,24 @@ function shortenPath(p) {
|
|
|
253
266
|
const home = process.env.HOME ?? "";
|
|
254
267
|
return home && p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
255
268
|
}
|
|
256
|
-
function
|
|
269
|
+
function formatRefreshStatus(lastRefresh, intervalMinutes) {
|
|
270
|
+
const time = lastRefresh.toLocaleTimeString();
|
|
271
|
+
return `\u27F3 Last refreshed ${time} \xB7 every ${intervalMinutes}m`;
|
|
272
|
+
}
|
|
273
|
+
function reconcileSelectedIndex(items, prevPath, prevIndex) {
|
|
274
|
+
if (items.length === 0) return 0;
|
|
275
|
+
if (prevPath) {
|
|
276
|
+
const found = items.findIndex((w) => w.path === prevPath);
|
|
277
|
+
if (found !== -1) return found;
|
|
278
|
+
}
|
|
279
|
+
return Math.min(Math.max(0, prevIndex), items.length - 1);
|
|
280
|
+
}
|
|
281
|
+
function buildListLayout(items, selectedIndex, query, mode, lastRefresh = null, intervalMinutes = 0) {
|
|
257
282
|
const header = [];
|
|
283
|
+
if (lastRefresh && intervalMinutes > 0) {
|
|
284
|
+
header.push(pc.dim(formatRefreshStatus(lastRefresh, intervalMinutes)));
|
|
285
|
+
header.push("");
|
|
286
|
+
}
|
|
258
287
|
if (mode === "global") {
|
|
259
288
|
header.push(
|
|
260
289
|
pc.dim("\u2139 Not in a git repository \u2014 showing all registered worktrees")
|
|
@@ -281,30 +310,34 @@ function buildListLayout(items, selectedIndex, query, mode) {
|
|
|
281
310
|
i++;
|
|
282
311
|
}
|
|
283
312
|
}
|
|
284
|
-
const createHint = mode === "repo" ? " \xB7 C create" : "";
|
|
285
313
|
const footer = [
|
|
286
|
-
pc.dim(
|
|
314
|
+
pc.dim(
|
|
315
|
+
"\u2195 navigate \xB7 Enter open \xB7 D delete \xB7 P prune \xB7 C create \xB7 A agent \xB7 Q quit"
|
|
316
|
+
)
|
|
287
317
|
];
|
|
288
318
|
return { header, body, footer, itemSpans };
|
|
289
319
|
}
|
|
290
|
-
function clampScroll(offset, span,
|
|
291
|
-
const maxOffset = Math.max(0, bodyLength -
|
|
320
|
+
function clampScroll(offset, span, viewportHeight2, bodyLength) {
|
|
321
|
+
const maxOffset = Math.max(0, bodyLength - viewportHeight2);
|
|
292
322
|
let next = offset;
|
|
293
323
|
if (span.start < next) next = span.start;
|
|
294
|
-
if (span.end >= next +
|
|
324
|
+
if (span.end >= next + viewportHeight2) next = span.end - viewportHeight2 + 1;
|
|
295
325
|
return Math.max(0, Math.min(next, maxOffset));
|
|
296
326
|
}
|
|
297
|
-
function composeView(layout, offset,
|
|
327
|
+
function composeView(layout, offset, viewportHeight2) {
|
|
298
328
|
const { header, body, footer } = layout;
|
|
299
|
-
const visible = body.slice(offset, offset +
|
|
300
|
-
while (visible.length <
|
|
329
|
+
const visible = body.slice(offset, offset + viewportHeight2);
|
|
330
|
+
while (visible.length < viewportHeight2) visible.push("");
|
|
301
331
|
const topSlot = offset > 0 ? pc.dim(" \u2191 more") : "";
|
|
302
|
-
const bottomSlot = offset +
|
|
332
|
+
const bottomSlot = offset + viewportHeight2 < body.length ? pc.dim(" \u2193 more") : "";
|
|
303
333
|
return [...header, topSlot, ...visible, bottomSlot, ...footer].join("\n");
|
|
304
334
|
}
|
|
305
335
|
function fixedHeight(layout) {
|
|
306
336
|
return layout.header.length + layout.footer.length + 2;
|
|
307
337
|
}
|
|
338
|
+
function viewportHeight(layout, rows) {
|
|
339
|
+
return Math.max(1, rows - fixedHeight(layout) - 1);
|
|
340
|
+
}
|
|
308
341
|
function setupRawMode() {
|
|
309
342
|
process.stdin.setRawMode(true);
|
|
310
343
|
process.stdin.resume();
|
|
@@ -315,6 +348,17 @@ function cleanupRawMode() {
|
|
|
315
348
|
process.stdin.pause();
|
|
316
349
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
317
350
|
}
|
|
351
|
+
function waitForKeypress() {
|
|
352
|
+
return new Promise((resolve) => {
|
|
353
|
+
process.stdin.setRawMode(true);
|
|
354
|
+
process.stdin.resume();
|
|
355
|
+
process.stdin.once("data", () => {
|
|
356
|
+
process.stdin.setRawMode(false);
|
|
357
|
+
process.stdin.pause();
|
|
358
|
+
resolve();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
318
362
|
function renderRepoPicker(repos, selectedIndex, query) {
|
|
319
363
|
const lines = [];
|
|
320
364
|
lines.push(pc.dim("\u2139 Not in a git repository \u2014 select a repo to create in"));
|
|
@@ -331,12 +375,12 @@ function renderRepoPicker(repos, selectedIndex, query) {
|
|
|
331
375
|
lines.push(pc.dim("\u2195 navigate \xB7 Enter select \xB7 Q quit"));
|
|
332
376
|
return lines.join("\n");
|
|
333
377
|
}
|
|
334
|
-
async function runRepoPicker(repos) {
|
|
378
|
+
async function runRepoPicker(repos, initialRepo) {
|
|
335
379
|
const filterRepos = (all, q) => q ? all.filter(
|
|
336
380
|
(p) => path2.basename(p).toLowerCase().includes(q.toLowerCase())
|
|
337
381
|
) : all;
|
|
338
382
|
let query = "";
|
|
339
|
-
let selectedIndex = 0;
|
|
383
|
+
let selectedIndex = initialRepo ? Math.max(0, repos.indexOf(initialRepo)) : 0;
|
|
340
384
|
let filtered = repos;
|
|
341
385
|
const render = () => {
|
|
342
386
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
@@ -408,8 +452,8 @@ function renderBranchInput(repoName, branch, error) {
|
|
|
408
452
|
lines.push(pc.dim("Enter confirm \xB7 Esc cancel"));
|
|
409
453
|
return lines.join("\n");
|
|
410
454
|
}
|
|
411
|
-
async function runBranchInput(repoRoot) {
|
|
412
|
-
let branch =
|
|
455
|
+
async function runBranchInput(repoRoot, initial = "") {
|
|
456
|
+
let branch = initial;
|
|
413
457
|
let error;
|
|
414
458
|
const repoName = path2.basename(repoRoot);
|
|
415
459
|
const render = () => {
|
|
@@ -452,15 +496,36 @@ async function runBranchInput(repoRoot) {
|
|
|
452
496
|
process.stdin.on("data", onData);
|
|
453
497
|
});
|
|
454
498
|
}
|
|
455
|
-
async function
|
|
499
|
+
async function runWizard(steps) {
|
|
500
|
+
let i = 0;
|
|
501
|
+
while (i < steps.length) {
|
|
502
|
+
if (await steps[i]()) {
|
|
503
|
+
i++;
|
|
504
|
+
} else if (--i < 0) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
async function runInteractiveList(allItems, mode, handlers, options = {}) {
|
|
511
|
+
const { autoRefreshMinutes = 0 } = options;
|
|
512
|
+
const refreshEnabled = Number.isFinite(autoRefreshMinutes) && autoRefreshMinutes > 0;
|
|
456
513
|
let query = "";
|
|
457
514
|
let selectedIndex = 0;
|
|
458
515
|
let scrollOffset = 0;
|
|
459
516
|
let filtered = allItems;
|
|
517
|
+
let lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : null;
|
|
460
518
|
const render = () => {
|
|
461
519
|
const rows = process.stdout.rows ?? 24;
|
|
462
|
-
const layout = buildListLayout(
|
|
463
|
-
|
|
520
|
+
const layout = buildListLayout(
|
|
521
|
+
filtered,
|
|
522
|
+
selectedIndex,
|
|
523
|
+
query,
|
|
524
|
+
mode,
|
|
525
|
+
lastRefresh,
|
|
526
|
+
autoRefreshMinutes
|
|
527
|
+
);
|
|
528
|
+
const viewport = viewportHeight(layout, rows);
|
|
464
529
|
const span = layout.itemSpans[selectedIndex] ?? { start: 0, end: 0 };
|
|
465
530
|
scrollOffset = clampScroll(
|
|
466
531
|
scrollOffset,
|
|
@@ -475,6 +540,9 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
475
540
|
render();
|
|
476
541
|
return new Promise((resolve, reject) => {
|
|
477
542
|
let listenerActive = false;
|
|
543
|
+
let interacting = false;
|
|
544
|
+
let refreshing = false;
|
|
545
|
+
let refreshTimer = null;
|
|
478
546
|
const attachListener = () => {
|
|
479
547
|
if (!listenerActive) {
|
|
480
548
|
process.stdin.on("data", onData);
|
|
@@ -487,9 +555,35 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
487
555
|
process.stdout.removeListener("resize", render);
|
|
488
556
|
listenerActive = false;
|
|
489
557
|
};
|
|
558
|
+
const stopRefresh = () => {
|
|
559
|
+
if (refreshTimer !== null) {
|
|
560
|
+
clearInterval(refreshTimer);
|
|
561
|
+
refreshTimer = null;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
const tick = async () => {
|
|
565
|
+
if (interacting || refreshing) return;
|
|
566
|
+
refreshing = true;
|
|
567
|
+
try {
|
|
568
|
+
const prevPath = filtered[selectedIndex]?.path;
|
|
569
|
+
allItems = await handlers.refreshItems();
|
|
570
|
+
filtered = filterItems(allItems, query);
|
|
571
|
+
selectedIndex = reconcileSelectedIndex(
|
|
572
|
+
filtered,
|
|
573
|
+
prevPath,
|
|
574
|
+
selectedIndex
|
|
575
|
+
);
|
|
576
|
+
lastRefresh = /* @__PURE__ */ new Date();
|
|
577
|
+
if (!interacting) render();
|
|
578
|
+
} catch {
|
|
579
|
+
} finally {
|
|
580
|
+
refreshing = false;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
490
583
|
const onData = async (key) => {
|
|
491
584
|
try {
|
|
492
585
|
if (key === "" || key === "q" || key === "Q" || key === "\x1B") {
|
|
586
|
+
stopRefresh();
|
|
493
587
|
detachListener();
|
|
494
588
|
cleanupRawMode();
|
|
495
589
|
resolve();
|
|
@@ -502,6 +596,7 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
502
596
|
} else if (key === "\r") {
|
|
503
597
|
const item = filtered[selectedIndex];
|
|
504
598
|
if (item) {
|
|
599
|
+
stopRefresh();
|
|
505
600
|
detachListener();
|
|
506
601
|
cleanupRawMode();
|
|
507
602
|
handlers.onOpen(item);
|
|
@@ -516,6 +611,7 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
516
611
|
);
|
|
517
612
|
return;
|
|
518
613
|
}
|
|
614
|
+
interacting = true;
|
|
519
615
|
detachListener();
|
|
520
616
|
cleanupRawMode();
|
|
521
617
|
const confirmed = await handlers.onDelete(item);
|
|
@@ -529,19 +625,62 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
529
625
|
);
|
|
530
626
|
setupRawMode();
|
|
531
627
|
attachListener();
|
|
628
|
+
interacting = false;
|
|
532
629
|
render();
|
|
533
630
|
}
|
|
534
|
-
} else if (key === "
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
631
|
+
} else if (key === "p" || key === "P") {
|
|
632
|
+
interacting = true;
|
|
633
|
+
detachListener();
|
|
634
|
+
cleanupRawMode();
|
|
635
|
+
const removed = await handlers.onWipe(allItems);
|
|
636
|
+
if (removed.length > 0) {
|
|
637
|
+
const removedSet = new Set(removed);
|
|
638
|
+
allItems = allItems.filter((w) => !removedSet.has(w));
|
|
639
|
+
filtered = filtered.filter((w) => !removedSet.has(w));
|
|
540
640
|
} else {
|
|
541
|
-
process.stdout.write(
|
|
542
|
-
|
|
543
|
-
);
|
|
641
|
+
process.stdout.write(pc.dim("\nPress any key to continue\u2026"));
|
|
642
|
+
await waitForKeypress();
|
|
544
643
|
}
|
|
644
|
+
selectedIndex = Math.min(
|
|
645
|
+
selectedIndex,
|
|
646
|
+
Math.max(0, filtered.length - 1)
|
|
647
|
+
);
|
|
648
|
+
setupRawMode();
|
|
649
|
+
attachListener();
|
|
650
|
+
interacting = false;
|
|
651
|
+
render();
|
|
652
|
+
} else if (key === "c" || key === "C") {
|
|
653
|
+
interacting = true;
|
|
654
|
+
detachListener();
|
|
655
|
+
cleanupRawMode();
|
|
656
|
+
await handlers.onCreate();
|
|
657
|
+
allItems = await handlers.refreshItems();
|
|
658
|
+
filtered = filterItems(allItems, query);
|
|
659
|
+
selectedIndex = Math.min(
|
|
660
|
+
selectedIndex,
|
|
661
|
+
Math.max(0, filtered.length - 1)
|
|
662
|
+
);
|
|
663
|
+
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
664
|
+
setupRawMode();
|
|
665
|
+
attachListener();
|
|
666
|
+
interacting = false;
|
|
667
|
+
render();
|
|
668
|
+
} else if (key === "a" || key === "A") {
|
|
669
|
+
interacting = true;
|
|
670
|
+
detachListener();
|
|
671
|
+
cleanupRawMode();
|
|
672
|
+
await handlers.onAgent();
|
|
673
|
+
allItems = await handlers.refreshItems();
|
|
674
|
+
filtered = filterItems(allItems, query);
|
|
675
|
+
selectedIndex = Math.min(
|
|
676
|
+
selectedIndex,
|
|
677
|
+
Math.max(0, filtered.length - 1)
|
|
678
|
+
);
|
|
679
|
+
lastRefresh = refreshEnabled ? /* @__PURE__ */ new Date() : lastRefresh;
|
|
680
|
+
setupRawMode();
|
|
681
|
+
attachListener();
|
|
682
|
+
interacting = false;
|
|
683
|
+
render();
|
|
545
684
|
} else if (key === "\x7F") {
|
|
546
685
|
query = query.slice(0, -1);
|
|
547
686
|
filtered = filterItems(allItems, query);
|
|
@@ -556,12 +695,16 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
556
695
|
render();
|
|
557
696
|
}
|
|
558
697
|
} catch (err) {
|
|
698
|
+
stopRefresh();
|
|
559
699
|
detachListener();
|
|
560
700
|
cleanupRawMode();
|
|
561
701
|
reject(err);
|
|
562
702
|
}
|
|
563
703
|
};
|
|
564
704
|
attachListener();
|
|
705
|
+
if (refreshEnabled) {
|
|
706
|
+
refreshTimer = setInterval(tick, autoRefreshMinutes * 6e4);
|
|
707
|
+
}
|
|
565
708
|
});
|
|
566
709
|
}
|
|
567
710
|
|
|
@@ -572,6 +715,7 @@ export {
|
|
|
572
715
|
removeWorktree,
|
|
573
716
|
listWorktreeDirtyFiles,
|
|
574
717
|
branchExists,
|
|
718
|
+
isBranchMerged,
|
|
575
719
|
setUpstreamTracking,
|
|
576
720
|
fetchRemote,
|
|
577
721
|
resolveWorktreePath,
|
|
@@ -581,5 +725,6 @@ export {
|
|
|
581
725
|
runCommands,
|
|
582
726
|
runRepoPicker,
|
|
583
727
|
runBranchInput,
|
|
728
|
+
runWizard,
|
|
584
729
|
runInteractiveList
|
|
585
730
|
};
|
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.
|
|
7
|
-
const { runList } = await import("./list-
|
|
6
|
+
program.name("wt").description("Git worktree manager").version("0.5.0-pr20.gd7a2b6f").action(async () => {
|
|
7
|
+
const { runList } = await import("./list-Z7A74ARA.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-CB5I7ZRF.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(
|
|
@@ -17,21 +17,27 @@ program.command("agent <branch> <plan_prompt>").description("Create a worktree a
|
|
|
17
17
|
"plan"
|
|
18
18
|
).action(
|
|
19
19
|
async (branch, planPrompt, options) => {
|
|
20
|
-
const { createAgentWorktree } = await import("./agent-
|
|
20
|
+
const { createAgentWorktree } = await import("./agent-NMJZAZGW.js");
|
|
21
21
|
await createAgentWorktree(branch, planPrompt, { mode: options.mode });
|
|
22
22
|
}
|
|
23
23
|
);
|
|
24
|
+
program.command("prune").description(
|
|
25
|
+
"Remove worktrees whose branch has been merged into the base branch"
|
|
26
|
+
).action(async () => {
|
|
27
|
+
const { runPrune } = await import("./prune-XSBBNQQY.js");
|
|
28
|
+
await runPrune();
|
|
29
|
+
});
|
|
24
30
|
program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
|
|
25
31
|
if (options.path) {
|
|
26
|
-
const { printConfigPath } = await import("./config-
|
|
32
|
+
const { printConfigPath } = await import("./config-QLWPW2UZ.js");
|
|
27
33
|
printConfigPath();
|
|
28
34
|
} else {
|
|
29
|
-
const { openConfig } = await import("./config-
|
|
35
|
+
const { openConfig } = await import("./config-QLWPW2UZ.js");
|
|
30
36
|
openConfig();
|
|
31
37
|
}
|
|
32
38
|
});
|
|
33
39
|
program.command("skill").description("Print the wt skill file to stdout").action(async () => {
|
|
34
|
-
const { printSkill } = await import("./skill-
|
|
40
|
+
const { printSkill } = await import("./skill-DJ2CRU4O.js");
|
|
35
41
|
printSkill();
|
|
36
42
|
});
|
|
37
43
|
await program.parseAsync(process.argv);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getConfigFilePath
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QVYSEMSL.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/config.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
@@ -19,6 +19,7 @@ function openConfig(cwd) {
|
|
|
19
19
|
process.exit(1);
|
|
20
20
|
});
|
|
21
21
|
child.on("close", (code) => process.exit(code ?? 0));
|
|
22
|
+
return child;
|
|
22
23
|
}
|
|
23
24
|
export {
|
|
24
25
|
openConfig,
|
|
@@ -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-EVCULRZW.js";
|
|
8
|
+
import "./chunk-O5GOGQUU.js";
|
|
9
|
+
import "./chunk-QVYSEMSL.js";
|
|
10
10
|
export {
|
|
11
11
|
createWorktree,
|
|
12
12
|
openConfiguredIde,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildMergedPredicate,
|
|
4
|
+
deleteWorktree,
|
|
5
|
+
prepareListItems,
|
|
6
|
+
runList,
|
|
7
|
+
selectWipeCandidates,
|
|
8
|
+
wipeWorktrees
|
|
9
|
+
} from "./chunk-NDDDPDNZ.js";
|
|
10
|
+
import "./chunk-O5GOGQUU.js";
|
|
11
|
+
import "./chunk-QVYSEMSL.js";
|
|
12
|
+
export {
|
|
13
|
+
buildMergedPredicate,
|
|
14
|
+
deleteWorktree,
|
|
15
|
+
prepareListItems,
|
|
16
|
+
runList,
|
|
17
|
+
selectWipeCandidates,
|
|
18
|
+
wipeWorktrees
|
|
19
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
prepareListItems,
|
|
4
|
+
wipeWorktrees
|
|
5
|
+
} from "./chunk-NDDDPDNZ.js";
|
|
6
|
+
import "./chunk-O5GOGQUU.js";
|
|
7
|
+
import {
|
|
8
|
+
createStore
|
|
9
|
+
} from "./chunk-QVYSEMSL.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
|
+
};
|
|
@@ -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- `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 `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 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 --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| `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_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
|
+
};
|
package/package.json
CHANGED
package/dist/list-WC5GM3PI.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
getRegisteredRepos,
|
|
4
|
-
getRepoRoot,
|
|
5
|
-
listWorktreeDirtyFiles,
|
|
6
|
-
listWorktrees,
|
|
7
|
-
openIde,
|
|
8
|
-
registerRepo,
|
|
9
|
-
removeWorktree,
|
|
10
|
-
runCommands,
|
|
11
|
-
runInteractiveList
|
|
12
|
-
} from "./chunk-XM4EABZV.js";
|
|
13
|
-
import {
|
|
14
|
-
createStore,
|
|
15
|
-
getEffectiveConfig
|
|
16
|
-
} from "./chunk-FNAMNRUH.js";
|
|
17
|
-
|
|
18
|
-
// src/commands/list.ts
|
|
19
|
-
import * as clack from "@clack/prompts";
|
|
20
|
-
import pc from "picocolors";
|
|
21
|
-
async function prepareListItems(options = {}) {
|
|
22
|
-
const { cwd = process.cwd(), store = createStore() } = options;
|
|
23
|
-
let repoRoot = null;
|
|
24
|
-
try {
|
|
25
|
-
repoRoot = getRepoRoot(cwd);
|
|
26
|
-
} catch {
|
|
27
|
-
}
|
|
28
|
-
if (repoRoot) {
|
|
29
|
-
registerRepo(repoRoot, store);
|
|
30
|
-
const items2 = listWorktrees(repoRoot, cwd);
|
|
31
|
-
return { items: items2, mode: "repo", repoRoot };
|
|
32
|
-
}
|
|
33
|
-
const repos = getRegisteredRepos(store);
|
|
34
|
-
const items = repos.flatMap((repo) => {
|
|
35
|
-
try {
|
|
36
|
-
return listWorktrees(repo, cwd);
|
|
37
|
-
} catch {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
return { items, mode: "global", repoRoot: null };
|
|
42
|
-
}
|
|
43
|
-
async function runList(options = {}) {
|
|
44
|
-
const { store = createStore(), cwd = process.cwd() } = options;
|
|
45
|
-
const { items, mode, repoRoot } = await prepareListItems({ cwd, store });
|
|
46
|
-
if (items.length === 0 && mode === "global") {
|
|
47
|
-
console.log(
|
|
48
|
-
pc.dim(
|
|
49
|
-
"No repos registered. Run `wt create` inside a repo to get started."
|
|
50
|
-
)
|
|
51
|
-
);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
await runInteractiveList(items, mode, {
|
|
55
|
-
onOpen: (item) => {
|
|
56
|
-
const config = getEffectiveConfig(item.repoRoot, store);
|
|
57
|
-
openIde(config.ide, config.ide_open_args, item.path);
|
|
58
|
-
},
|
|
59
|
-
onDelete: async (item) => {
|
|
60
|
-
const confirmed = await clack.confirm({
|
|
61
|
-
message: `Remove worktree ${pc.bold(item.branch)}? This cannot be undone.`
|
|
62
|
-
});
|
|
63
|
-
if (clack.isCancel(confirmed) || !confirmed) return false;
|
|
64
|
-
const config = getEffectiveConfig(item.repoRoot, store);
|
|
65
|
-
if (config.teardown_commands.length > 0) {
|
|
66
|
-
console.log(pc.dim("Running teardown commands..."));
|
|
67
|
-
const result = await runCommands(config.teardown_commands, item.path);
|
|
68
|
-
if (!result.success) {
|
|
69
|
-
clack.log.warn(
|
|
70
|
-
`Teardown command failed: ${result.failedCommand} (exit code ${result.exitCode})`
|
|
71
|
-
);
|
|
72
|
-
const proceed = await clack.confirm({
|
|
73
|
-
message: `Delete ${pc.bold(item.branch)} anyway?`
|
|
74
|
-
});
|
|
75
|
-
if (clack.isCancel(proceed) || !proceed) return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
try {
|
|
79
|
-
removeWorktree(item.repoRoot, item.path);
|
|
80
|
-
console.log(pc.green(`\u2713 Removed ${item.branch}`));
|
|
81
|
-
return true;
|
|
82
|
-
} catch (err) {
|
|
83
|
-
const msg = String(err);
|
|
84
|
-
if (msg.includes("cannot be moved or removed")) {
|
|
85
|
-
clack.log.warn(
|
|
86
|
-
"Worktree contains git submodules, which prevent standard removal."
|
|
87
|
-
);
|
|
88
|
-
const force = await clack.confirm({
|
|
89
|
-
message: `Force delete ${pc.bold(item.branch)}? The worktree directory will be removed directly.`
|
|
90
|
-
});
|
|
91
|
-
if (clack.isCancel(force) || !force) return false;
|
|
92
|
-
try {
|
|
93
|
-
removeWorktree(item.repoRoot, item.path, true);
|
|
94
|
-
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
95
|
-
return true;
|
|
96
|
-
} catch (err2) {
|
|
97
|
-
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (msg.includes("modified or untracked files")) {
|
|
102
|
-
const dirty = listWorktreeDirtyFiles(item.path);
|
|
103
|
-
if (dirty.length > 0) {
|
|
104
|
-
clack.log.warn(
|
|
105
|
-
`Worktree has uncommitted changes:
|
|
106
|
-
${dirty.map((f) => ` ${f}`).join("\n")}`
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
const force = await clack.confirm({
|
|
110
|
-
message: `Force delete ${pc.bold(item.branch)}? All changes will be lost.`
|
|
111
|
-
});
|
|
112
|
-
if (clack.isCancel(force) || !force) return false;
|
|
113
|
-
try {
|
|
114
|
-
removeWorktree(item.repoRoot, item.path, true);
|
|
115
|
-
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
116
|
-
return true;
|
|
117
|
-
} catch (err2) {
|
|
118
|
-
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
console.error(pc.red(`\u2717 Failed to remove: ${msg}`));
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
onCreate: async () => {
|
|
127
|
-
if (repoRoot) {
|
|
128
|
-
const { createWorktree } = await import("./create-WRMXQIGN.js");
|
|
129
|
-
await createWorktree(void 0, { cwd: repoRoot, store });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
export {
|
|
135
|
-
prepareListItems,
|
|
136
|
-
runList
|
|
137
|
-
};
|
package/dist/skill-FEUVQMK6.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 / `j`/`k` \u2014 navigate\n- `Enter` \u2014 open worktree in IDE\n- `d` \u2014 delete worktree\n- `c` \u2014 create new worktree (repo mode only)\n- `/` \u2014 search\n- `q` / `Esc` \u2014 quit\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
|
-
};
|