@cestoliv/wt 0.5.0 → 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 +51 -12
- package/SKILL.md +39 -8
- package/dist/{agent-Z3YCY245.js → agent-QYE5UNA3.js} +17 -6
- package/dist/chunk-BJDZXGOC.js +271 -0
- package/dist/{chunk-QGSJG72F.js → chunk-OA55NRNT.js} +2 -2
- package/dist/{chunk-FNAMNRUH.js → chunk-OUWQ6NIV.js} +3 -1
- package/dist/{chunk-GHYUCETL.js → chunk-XOP26UY4.js} +272 -28
- package/dist/cli.js +14 -9
- package/dist/{config-RFATE2PF.js → config-QSYG3JDC.js} +1 -1
- package/dist/{create-XKF574AL.js → create-K4OQIX7A.js} +3 -3
- package/dist/list-GLLMKIKE.js +19 -0
- package/dist/prune-HKCDPXQD.js +31 -0
- package/dist/skill-T5VOI4ZB.js +9 -0
- package/package.json +1 -1
- package/dist/list-XHV4ODXW.js +0 -204
- package/dist/skill-MVKLVB5V.js +0 -9
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
|
|
|
@@ -37,7 +40,8 @@ infer from this project. Write the result to the config file (find its path with
|
|
|
37
40
|
wt # Browse worktrees (interactive TUI)
|
|
38
41
|
wt create my-feat # New worktree, opens your IDE
|
|
39
42
|
wt agent my-feat "Plan the feature" # New worktree + AI agent in Zed (macOS)
|
|
40
|
-
wt agent fix-bug "Fix bug" --mode auto # Use auto mode instead of
|
|
43
|
+
wt agent fix-bug "Fix bug" --mode auto # Use auto mode instead of the default
|
|
44
|
+
wt prune # Remove merged worktrees (per-branch confirm)
|
|
41
45
|
wt config # Edit config in $EDITOR
|
|
42
46
|
wt skill # Print the skill file (for AI agents)
|
|
43
47
|
```
|
|
@@ -51,14 +55,15 @@ wt agent refactor "Refactor API layer" --mode default
|
|
|
51
55
|
```
|
|
52
56
|
|
|
53
57
|
Creates a worktree exactly like `wt create`, then auto-starts your agent
|
|
54
|
-
(default `claude
|
|
55
|
-
pre-filled with your prompt and left interactive for you to take over.
|
|
58
|
+
(default `claude`, run with `--permission-mode default`) in Zed's integrated
|
|
59
|
+
terminal — pre-filled with your prompt and left interactive for you to take over.
|
|
56
60
|
|
|
57
|
-
**Available modes** (`--mode`, defaults to `
|
|
61
|
+
**Available modes** (`--mode`, defaults to `default`; change the default with
|
|
62
|
+
the `agent_mode` config key):
|
|
58
63
|
|
|
59
|
-
- `default` — Standard interactive mode with approval for each action
|
|
64
|
+
- `default` — Standard interactive mode with approval for each action (default)
|
|
60
65
|
- `acceptEdits` — Allow file changes but keep command execution controlled
|
|
61
|
-
- `plan` — Architecture-first mode with no surprise mutations
|
|
66
|
+
- `plan` — Architecture-first mode with no surprise mutations
|
|
62
67
|
- `auto` — Claude's safety model makes decisions instead of prompting
|
|
63
68
|
- `dontAsk` — Minimal interruptions in trusted environments
|
|
64
69
|
- `bypassPermissions` — Skip all permission checks (dangerous, CI/sandbox only)
|
|
@@ -85,12 +90,12 @@ An interactive, fuzzy-searchable list of your worktrees:
|
|
|
85
90
|
|
|
86
91
|
```
|
|
87
92
|
MY-PROJECT
|
|
88
|
-
▶ main ~/dev/my-project
|
|
93
|
+
▶ main (main) ~/dev/my-project
|
|
89
94
|
fix: resolve auth bug (2h ago)
|
|
90
95
|
feat/dashboard ~/dev/my-project-feat-dashboard
|
|
91
96
|
wip: add chart component (1d ago)
|
|
92
97
|
|
|
93
|
-
↕ navigate · Enter open · D delete · C create · A agent · Q quit
|
|
98
|
+
↕ navigate · Enter open · D delete · P prune · C create · A agent · Q quit
|
|
94
99
|
```
|
|
95
100
|
|
|
96
101
|
Type to fuzzy-filter branches instantly. Inside a repo it shows that repo's
|
|
@@ -104,8 +109,12 @@ and a permission mode — **worktree (repo → branch) → plan prompt → permi
|
|
|
104
109
|
mode**. Pressing `Esc` steps back to the previous question (answers preserved),
|
|
105
110
|
or back to the list from the first step. After creating, the list **refreshes
|
|
106
111
|
and stays open** (preserving your search and cursor) instead of exiting — only
|
|
107
|
-
`Enter` and `Q`/`Esc` leave the TUI.
|
|
108
|
-
|
|
112
|
+
`Enter` and `Q`/`Esc` leave the TUI. `P` prunes every worktree whose branch has
|
|
113
|
+
already been merged (see [`wt prune`](#prune--wt-prune) below). Note that
|
|
114
|
+
`a`/`c`/`d`/`p` are command keys, so they can't be typed into the search box.
|
|
115
|
+
|
|
116
|
+
The main worktree is tagged `(main)` and is protected — `D` only removes linked
|
|
117
|
+
worktrees, never the main repository.
|
|
109
118
|
|
|
110
119
|
## Create — `wt create [branch]`
|
|
111
120
|
|
|
@@ -121,6 +130,34 @@ registered repos.
|
|
|
121
130
|
If the path already exists, `wt create` offers to open it in your IDE instead of
|
|
122
131
|
erroring (in a non-interactive shell it exits non-zero).
|
|
123
132
|
|
|
133
|
+
## Prune — `wt prune`
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
wt prune # remove every merged worktree, one confirmation per branch
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Cleans up the worktrees you're done with: it finds every worktree whose branch
|
|
140
|
+
has already been merged into the base branch (`base_branch`, default
|
|
141
|
+
`origin/main`) and removes it — **always confirming each branch individually**,
|
|
142
|
+
and force-confirming when git refuses (submodules or uncommitted changes), just
|
|
143
|
+
like a manual `D` delete. The branch itself stays; only the worktree is removed.
|
|
144
|
+
Your `teardown_commands` run before each removal.
|
|
145
|
+
|
|
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
|
|
156
|
+
the remote first; if the base ref can't be resolved (offline, missing), it
|
|
157
|
+
removes nothing. The TUI exposes the same action under the `P` key. Works in
|
|
158
|
+
repo mode and across all registered repos in global mode (each against its own
|
|
159
|
+
`base_branch`).
|
|
160
|
+
|
|
124
161
|
## Configuration
|
|
125
162
|
|
|
126
163
|
Edit with `wt config` (`wt config --path` prints the file location —
|
|
@@ -134,8 +171,10 @@ Edit with `wt config` (`wt config --path` prints the file location —
|
|
|
134
171
|
| `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
|
|
135
172
|
| `setup_commands` | `[]` | Commands to run in new worktrees |
|
|
136
173
|
| `teardown_commands` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`) |
|
|
137
|
-
| `agent_command` | `"claude
|
|
174
|
+
| `agent_command` | `"claude"` | Base command; `--permission-mode <mode>` injected, then prompt appended |
|
|
175
|
+
| `agent_mode` | `"default"` | Default permission mode for `wt agent` (overridden by `--mode`) |
|
|
138
176
|
| `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs and presses |
|
|
177
|
+
| `auto_refresh_minutes`| `5` | How often the interactive list re-fetches worktrees (shows a "last refreshed" header); `0` disables it |
|
|
139
178
|
| `repo_overrides` | `{}` | Per-repo overrides for any key above |
|
|
140
179
|
|
|
141
180
|
Override any key per repo:
|
package/SKILL.md
CHANGED
|
@@ -17,7 +17,8 @@ Launch the interactive TUI. Shows worktrees for the current repo (repo mode) or
|
|
|
17
17
|
|
|
18
18
|
- Arrow keys — navigate
|
|
19
19
|
- `Enter` — open worktree in IDE (exits the TUI)
|
|
20
|
-
- `D` — delete worktree
|
|
20
|
+
- `D` — delete worktree (the main worktree is tagged `(main)` and cannot be deleted — only linked worktrees can)
|
|
21
|
+
- `P` — prune all merged worktrees (per-branch confirmation)
|
|
21
22
|
- `C` — create a new worktree (works in both repo and global mode)
|
|
22
23
|
- `A` — create a worktree and start an AI agent in it (works in both modes)
|
|
23
24
|
- type to search · `Backspace` — edit search
|
|
@@ -38,8 +39,8 @@ After a create or agent action the TUI **refreshes and stays open** on the list
|
|
|
38
39
|
(your search and cursor are preserved) rather than exiting — only `Enter` (open)
|
|
39
40
|
and `Q`/`Esc` exit.
|
|
40
41
|
|
|
41
|
-
Because `a`/`A`, `c`/`C`,
|
|
42
|
-
letters can't be typed into the search box.
|
|
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.
|
|
43
44
|
|
|
44
45
|
### `wt create [branch]`
|
|
45
46
|
|
|
@@ -65,11 +66,12 @@ wt agent feature/fix 'Fix the bug in payment processing' --mode auto
|
|
|
65
66
|
wt agent refactor/api 'Refactor the API layer' --mode default
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
The `--mode` flag sets Claude Code's permission mode (defaults to `
|
|
69
|
+
The `--mode` flag sets Claude Code's permission mode (defaults to `default`;
|
|
70
|
+
change the default with the `agent_mode` config key):
|
|
69
71
|
|
|
70
|
-
- `default` — Standard interactive mode with approval for each action
|
|
72
|
+
- `default` — Standard interactive mode with approval for each action (default)
|
|
71
73
|
- `acceptEdits` — Allow file changes but keep command execution controlled
|
|
72
|
-
- `plan` — Architecture-first mode with no surprise mutations
|
|
74
|
+
- `plan` — Architecture-first mode with no surprise mutations
|
|
73
75
|
- `auto` — Claude's safety model makes decisions instead of prompting
|
|
74
76
|
- `dontAsk` — Minimal interruptions in trusted environments
|
|
75
77
|
- `bypassPermissions` — Skip all permission checks (dangerous, CI/sandbox only)
|
|
@@ -97,6 +99,33 @@ If the worktree path already exists, `wt agent` prompts you to **open it in the
|
|
|
97
99
|
IDE**, **open it and start the agent**, or **quit** — instead of erroring. (In a
|
|
98
100
|
non-interactive shell it errors with a non-zero exit instead of prompting.)
|
|
99
101
|
|
|
102
|
+
### `wt prune`
|
|
103
|
+
|
|
104
|
+
Remove every worktree whose branch has already been merged into the base
|
|
105
|
+
branch (`base_branch`, default `origin/main`). Each candidate is confirmed
|
|
106
|
+
individually — and force-confirmed when git refuses (submodules / uncommitted
|
|
107
|
+
changes), exactly like a manual `d` delete. The branch itself is left intact;
|
|
108
|
+
only the worktree is removed.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
wt prune # review and remove merged worktrees, one prompt per branch
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Merge detection 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
|
|
125
|
+
base ref can't be resolved (e.g. offline), nothing is removed. Works in repo
|
|
126
|
+
mode (current repo) and global mode (all registered repos, each against its own
|
|
127
|
+
`base_branch`). The TUI exposes the same action under the `p` key.
|
|
128
|
+
|
|
100
129
|
### `wt config`
|
|
101
130
|
|
|
102
131
|
Open the global config file in `$EDITOR` (defaults to `nano`).
|
|
@@ -124,14 +153,16 @@ Config is stored as JSON. Get the path with `wt config --path`.
|
|
|
124
153
|
| `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 |
|
|
125
154
|
| `ide` | `string` | `"zed"` | IDE command to open worktrees with |
|
|
126
155
|
| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |
|
|
127
|
-
| `agent_command` | `string` | `"claude
|
|
156
|
+
| `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 |
|
|
157
|
+
| `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` |
|
|
128
158
|
| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |
|
|
159
|
+
| `auto_refresh_minutes`| `number` | `5` | How often the interactive list (`wt`) re-fetches worktrees and updates the "last refreshed" header; `0` disables auto-refresh |
|
|
129
160
|
| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |
|
|
130
161
|
| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |
|
|
131
162
|
|
|
132
163
|
### Per-repo overrides
|
|
133
164
|
|
|
134
|
-
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:
|
|
165
|
+
Override 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:
|
|
135
166
|
|
|
136
167
|
```json
|
|
137
168
|
{
|
|
@@ -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-OA55NRNT.js";
|
|
7
|
+
import "./chunk-XOP26UY4.js";
|
|
8
|
+
import "./chunk-OUWQ6NIV.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/agent.ts
|
|
11
11
|
import * as clack from "@clack/prompts";
|
|
@@ -424,19 +424,30 @@ var VALID_MODES = [
|
|
|
424
424
|
"dontAsk",
|
|
425
425
|
"bypassPermissions"
|
|
426
426
|
];
|
|
427
|
+
var isValidMode = (mode) => VALID_MODES.includes(mode);
|
|
427
428
|
var CLEANUP_DELAY_MS = 2e4;
|
|
428
429
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
429
430
|
async function createAgentWorktree(branch, planPrompt, options = {}) {
|
|
430
|
-
|
|
431
|
-
if (!VALID_MODES.includes(mode)) {
|
|
431
|
+
if (options.mode !== void 0 && !isValidMode(options.mode)) {
|
|
432
432
|
console.error(
|
|
433
|
-
pc.red(
|
|
433
|
+
pc.red(
|
|
434
|
+
`Invalid mode "${options.mode}". Valid modes: ${VALID_MODES.join(", ")}`
|
|
435
|
+
)
|
|
434
436
|
);
|
|
435
437
|
process.exit(1);
|
|
436
438
|
}
|
|
437
439
|
const prepared = await prepareWorktree(branch, options);
|
|
438
440
|
if (!prepared) return;
|
|
439
441
|
const { status, config, worktreePath } = prepared;
|
|
442
|
+
let mode = options.mode ?? config.agent_mode ?? "default";
|
|
443
|
+
if (!isValidMode(mode)) {
|
|
444
|
+
console.warn(
|
|
445
|
+
pc.yellow(
|
|
446
|
+
`\u26A0 Invalid agent_mode "${mode}" in config; using "default". Valid modes: ${VALID_MODES.join(", ")}`
|
|
447
|
+
)
|
|
448
|
+
);
|
|
449
|
+
mode = "default";
|
|
450
|
+
}
|
|
440
451
|
if (status === "exists") {
|
|
441
452
|
const prompt = options.existingWorktreePrompt ?? promptExistingWorktree;
|
|
442
453
|
const action = await prompt(worktreePath, { allowAgent: true });
|
|
@@ -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-XOP26UY4.js";
|
|
18
|
+
import {
|
|
19
|
+
createStore,
|
|
20
|
+
getEffectiveConfig,
|
|
21
|
+
getGlobalConfig
|
|
22
|
+
} from "./chunk-OUWQ6NIV.js";
|
|
23
|
+
|
|
24
|
+
// src/commands/list.ts
|
|
25
|
+
import * as clack from "@clack/prompts";
|
|
26
|
+
import pc from "picocolors";
|
|
27
|
+
function buildWorktreeSteps(repoRoot, store, state) {
|
|
28
|
+
const steps = [];
|
|
29
|
+
if (!repoRoot) {
|
|
30
|
+
const repos = getRegisteredRepos(store);
|
|
31
|
+
steps.push(async () => {
|
|
32
|
+
const picked = await runRepoPicker(repos, state.pickedRepo);
|
|
33
|
+
if (!picked) return false;
|
|
34
|
+
state.pickedRepo = picked;
|
|
35
|
+
return true;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
steps.push(async () => {
|
|
39
|
+
const entered = await runBranchInput(
|
|
40
|
+
state.pickedRepo,
|
|
41
|
+
state.branch ?? ""
|
|
42
|
+
);
|
|
43
|
+
if (!entered) return false;
|
|
44
|
+
state.branch = entered;
|
|
45
|
+
return true;
|
|
46
|
+
});
|
|
47
|
+
return steps;
|
|
48
|
+
}
|
|
49
|
+
async function prepareListItems(options = {}) {
|
|
50
|
+
const { cwd = process.cwd(), store = createStore() } = options;
|
|
51
|
+
let repoRoot = null;
|
|
52
|
+
try {
|
|
53
|
+
repoRoot = getRepoRoot(cwd);
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
if (repoRoot) {
|
|
57
|
+
registerRepo(repoRoot, store);
|
|
58
|
+
const items2 = listWorktrees(repoRoot, cwd);
|
|
59
|
+
return { items: items2, mode: "repo", repoRoot };
|
|
60
|
+
}
|
|
61
|
+
const repos = getRegisteredRepos(store);
|
|
62
|
+
const items = repos.flatMap((repo) => {
|
|
63
|
+
try {
|
|
64
|
+
return listWorktrees(repo, cwd);
|
|
65
|
+
} catch {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return { items, mode: "global", repoRoot: null };
|
|
70
|
+
}
|
|
71
|
+
async function runList(options = {}) {
|
|
72
|
+
const { store = createStore(), cwd = process.cwd() } = options;
|
|
73
|
+
const { items, mode, repoRoot } = await prepareListItems({ cwd, store });
|
|
74
|
+
if (items.length === 0 && mode === "global") {
|
|
75
|
+
console.log(
|
|
76
|
+
pc.dim(
|
|
77
|
+
"No repos registered. Run `wt create` inside a repo to get started."
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const autoRefreshMinutes = mode === "repo" && repoRoot ? getEffectiveConfig(repoRoot, store).auto_refresh_minutes : getGlobalConfig(store).auto_refresh_minutes;
|
|
83
|
+
await runInteractiveList(
|
|
84
|
+
items,
|
|
85
|
+
mode,
|
|
86
|
+
{
|
|
87
|
+
onOpen: (item) => {
|
|
88
|
+
const config = getEffectiveConfig(item.repoRoot, store);
|
|
89
|
+
openIde(config.ide, config.ide_open_args, item.path);
|
|
90
|
+
},
|
|
91
|
+
onDelete: (item) => deleteWorktree(item, store),
|
|
92
|
+
onWipe: (items2) => wipeWorktrees(items2, store, { fetch: true }),
|
|
93
|
+
onCreate: async () => {
|
|
94
|
+
const state = { pickedRepo: repoRoot ?? void 0 };
|
|
95
|
+
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
96
|
+
if (!await runWizard(steps)) return;
|
|
97
|
+
if (state.pickedRepo === void 0 || state.branch === void 0)
|
|
98
|
+
return;
|
|
99
|
+
const { createWorktree } = await import("./create-K4OQIX7A.js");
|
|
100
|
+
await createWorktree(state.branch, { cwd: state.pickedRepo, store });
|
|
101
|
+
},
|
|
102
|
+
onAgent: async () => {
|
|
103
|
+
const { createAgentWorktree, VALID_MODES } = await import("./agent-QYE5UNA3.js");
|
|
104
|
+
const state = {
|
|
105
|
+
pickedRepo: repoRoot ?? void 0
|
|
106
|
+
};
|
|
107
|
+
const steps = buildWorktreeSteps(repoRoot, store, state);
|
|
108
|
+
steps.push(async () => {
|
|
109
|
+
const entered = await clack.text({
|
|
110
|
+
message: "Plan prompt for the agent:",
|
|
111
|
+
initialValue: state.plan,
|
|
112
|
+
validate: (v) => !v || v.length === 0 ? "Required" : void 0
|
|
113
|
+
});
|
|
114
|
+
if (clack.isCancel(entered)) return false;
|
|
115
|
+
state.plan = entered;
|
|
116
|
+
return true;
|
|
117
|
+
});
|
|
118
|
+
steps.push(async () => {
|
|
119
|
+
const configuredMode = state.pickedRepo ? getEffectiveConfig(state.pickedRepo, store).agent_mode : void 0;
|
|
120
|
+
const chosen = await clack.select({
|
|
121
|
+
message: "Permission mode:",
|
|
122
|
+
initialValue: state.mode ?? configuredMode,
|
|
123
|
+
options: VALID_MODES.map((m) => ({ value: String(m), label: m }))
|
|
124
|
+
});
|
|
125
|
+
if (clack.isCancel(chosen)) return false;
|
|
126
|
+
state.mode = chosen;
|
|
127
|
+
return true;
|
|
128
|
+
});
|
|
129
|
+
if (!await runWizard(steps)) return;
|
|
130
|
+
if (state.pickedRepo === void 0 || state.branch === void 0 || state.plan === void 0)
|
|
131
|
+
return;
|
|
132
|
+
await createAgentWorktree(state.branch, state.plan, {
|
|
133
|
+
cwd: state.pickedRepo,
|
|
134
|
+
store,
|
|
135
|
+
mode: state.mode
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
refreshItems: async () => {
|
|
139
|
+
const refreshed = await prepareListItems({ cwd, store });
|
|
140
|
+
return refreshed.items;
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{ autoRefreshMinutes }
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
async function deleteWorktree(item, store) {
|
|
147
|
+
const confirmed = await clack.confirm({
|
|
148
|
+
message: `Remove worktree ${pc.bold(item.branch)}? This cannot be undone.`
|
|
149
|
+
});
|
|
150
|
+
if (clack.isCancel(confirmed) || !confirmed) return false;
|
|
151
|
+
const config = getEffectiveConfig(item.repoRoot, store);
|
|
152
|
+
if (config.teardown_commands.length > 0) {
|
|
153
|
+
console.log(pc.dim("Running teardown commands..."));
|
|
154
|
+
const result = await runCommands(config.teardown_commands, item.path);
|
|
155
|
+
if (!result.success) {
|
|
156
|
+
clack.log.warn(
|
|
157
|
+
`Teardown command failed: ${result.failedCommand} (exit code ${result.exitCode})`
|
|
158
|
+
);
|
|
159
|
+
const proceed = await clack.confirm({
|
|
160
|
+
message: `Delete ${pc.bold(item.branch)} anyway?`
|
|
161
|
+
});
|
|
162
|
+
if (clack.isCancel(proceed) || !proceed) return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
removeWorktree(item.repoRoot, item.path);
|
|
167
|
+
console.log(pc.green(`\u2713 Removed ${item.branch}`));
|
|
168
|
+
return true;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
const msg = String(err);
|
|
171
|
+
if (msg.includes("cannot be moved or removed")) {
|
|
172
|
+
clack.log.warn(
|
|
173
|
+
"Worktree contains git submodules, which prevent standard removal."
|
|
174
|
+
);
|
|
175
|
+
const force = await clack.confirm({
|
|
176
|
+
message: `Force delete ${pc.bold(item.branch)}? The worktree directory will be removed directly.`
|
|
177
|
+
});
|
|
178
|
+
if (clack.isCancel(force) || !force) return false;
|
|
179
|
+
try {
|
|
180
|
+
removeWorktree(item.repoRoot, item.path, true);
|
|
181
|
+
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
182
|
+
return true;
|
|
183
|
+
} catch (err2) {
|
|
184
|
+
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (msg.includes("modified or untracked files")) {
|
|
189
|
+
const dirty = listWorktreeDirtyFiles(item.path);
|
|
190
|
+
if (dirty.length > 0) {
|
|
191
|
+
clack.log.warn(
|
|
192
|
+
`Worktree has uncommitted changes:
|
|
193
|
+
${dirty.map((f) => ` ${f}`).join("\n")}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const force = await clack.confirm({
|
|
197
|
+
message: `Force delete ${pc.bold(item.branch)}? All changes will be lost.`
|
|
198
|
+
});
|
|
199
|
+
if (clack.isCancel(force) || !force) return false;
|
|
200
|
+
try {
|
|
201
|
+
removeWorktree(item.repoRoot, item.path, true);
|
|
202
|
+
console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
|
|
203
|
+
return true;
|
|
204
|
+
} catch (err2) {
|
|
205
|
+
console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.error(pc.red(`\u2717 Failed to remove: ${msg}`));
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function selectWipeCandidates(items, isMerged) {
|
|
214
|
+
return items.filter(
|
|
215
|
+
(wt) => !wt.isCurrent && !wt.isMain && wt.branch !== "(detached)" && isMerged(wt)
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
function buildMergedPredicate(store) {
|
|
219
|
+
return (wt) => {
|
|
220
|
+
const config = getEffectiveConfig(wt.repoRoot, store);
|
|
221
|
+
const base = config.base_branch;
|
|
222
|
+
const baseLocal = base.split("/", 2).slice(1).join("/") || base;
|
|
223
|
+
if (wt.branch === base || wt.branch === baseLocal) return false;
|
|
224
|
+
return isBranchMerged(wt.repoRoot, wt.branch, base);
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
async function wipeWorktrees(items, store, options = {}) {
|
|
228
|
+
if (options.fetch) {
|
|
229
|
+
const seen = /* @__PURE__ */ new Set();
|
|
230
|
+
for (const wt of items) {
|
|
231
|
+
if (seen.has(wt.repoRoot)) continue;
|
|
232
|
+
seen.add(wt.repoRoot);
|
|
233
|
+
const parts = getEffectiveConfig(wt.repoRoot, store).base_branch.split(
|
|
234
|
+
"/",
|
|
235
|
+
2
|
|
236
|
+
);
|
|
237
|
+
if (parts.length !== 2) continue;
|
|
238
|
+
const remote = parts[0] || "origin";
|
|
239
|
+
try {
|
|
240
|
+
fetchRemote(wt.repoRoot, remote);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
console.warn(
|
|
243
|
+
pc.yellow(
|
|
244
|
+
`\u26A0 Could not fetch from ${remote} \u2014 using local state${err instanceof Error ? ` (${err.message})` : ""}`
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const candidates = selectWipeCandidates(items, buildMergedPredicate(store));
|
|
251
|
+
if (candidates.length === 0) {
|
|
252
|
+
console.log(pc.dim("No merged worktrees to wipe."));
|
|
253
|
+
return [];
|
|
254
|
+
}
|
|
255
|
+
const removed = [];
|
|
256
|
+
for (const candidate of candidates) {
|
|
257
|
+
if (await deleteWorktree(candidate, store)) {
|
|
258
|
+
removed.push(candidate);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return removed;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export {
|
|
265
|
+
prepareListItems,
|
|
266
|
+
runList,
|
|
267
|
+
deleteWorktree,
|
|
268
|
+
selectWipeCandidates,
|
|
269
|
+
buildMergedPredicate,
|
|
270
|
+
wipeWorktrees
|
|
271
|
+
};
|
|
@@ -13,11 +13,11 @@ import {
|
|
|
13
13
|
runCommands,
|
|
14
14
|
runRepoPicker,
|
|
15
15
|
setUpstreamTracking
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XOP26UY4.js";
|
|
17
17
|
import {
|
|
18
18
|
createStore,
|
|
19
19
|
getEffectiveConfig
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-OUWQ6NIV.js";
|
|
21
21
|
|
|
22
22
|
// src/commands/create.ts
|
|
23
23
|
import { existsSync } from "fs";
|
|
@@ -68,8 +68,10 @@ var DEFAULT_CONFIG = {
|
|
|
68
68
|
teardown_commands: [],
|
|
69
69
|
ide: "zed",
|
|
70
70
|
ide_open_args: ["-n"],
|
|
71
|
-
agent_command: "claude
|
|
71
|
+
agent_command: "claude",
|
|
72
72
|
agent_trigger_chord: "ctrl-shift-cmd-c",
|
|
73
|
+
auto_refresh_minutes: 5,
|
|
74
|
+
agent_mode: "default",
|
|
73
75
|
repos: [],
|
|
74
76
|
repo_overrides: {}
|
|
75
77
|
};
|