@cestoliv/wt 0.1.0-pr5.gd63269d → 0.2.0-pr9.g997ac8d

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 CHANGED
@@ -1,51 +1,79 @@
1
1
  # wt
2
2
 
3
- A fast, interactive TUI for managing git worktrees.
3
+ A fast TUI for git worktrees — browse, create, open, and delete without leaving
4
+ the terminal. Plus **one-command AI agents**:
4
5
 
5
- Browse, create, open, and delete worktrees without leaving the terminal. Fuzzy search across branches, auto-open your IDE, and run setup commands on new worktrees — all from one tool.
6
+ ```bash
7
+ wt agent fix-auth "Plan the auth refactor"
8
+ ```
9
+
10
+ → spins up an isolated worktree and launches Claude in Zed, pre-loaded with your
11
+ prompt.
6
12
 
7
13
  ## Install
8
14
 
9
15
  ```bash
10
- npm install -g @cestoliv/wt
16
+ npm install -g @cestoliv/wt # also the update command
11
17
  ```
12
18
 
13
19
  Requires Node.js 20+ and Git. The command is `wt`.
14
20
 
15
- ### Update
21
+ ## Let your AI assistant set it up
16
22
 
17
- ```bash
18
- npm install -g @cestoliv/wt
23
+ Already using an AI coding assistant (Claude Code, Cursor, …)? Paste this prompt
24
+ it reads `wt skill` to learn the tool, then configures `wt` for you:
25
+
26
+ ```text
27
+ Run `wt skill` to learn how the `wt` CLI works and what its config options are.
28
+ Then configure it for me: choose sensible values for my editor, base branch,
29
+ setup commands, and the `wt agent` settings — ask me about anything you can't
30
+ infer from this project. Write the result to the config file (find its path with
31
+ `wt config --path`), then show me the final config.
19
32
  ```
20
33
 
21
- ### Pre-release builds
34
+ ## Quick start
22
35
 
23
- Add the `publish-dev` label to a PR to publish that branch as a unique,
24
- pinned prerelease version (e.g. `0.1.0-pr12.gabc1234`). The exact install
25
- command is posted as a comment on the PR. There is no rolling `dev` channel —
26
- each build is a distinct version you install explicitly.
36
+ ```bash
37
+ wt # Browse worktrees (interactive TUI)
38
+ wt create my-feat # New worktree, opens your IDE
39
+ wt agent my-feat "Plan the feature" # New worktree + AI agent in Zed (macOS)
40
+ wt config # Edit config in $EDITOR
41
+ wt skill # Print the skill file (for AI agents)
42
+ ```
27
43
 
28
- ## Quick Start
44
+ ## `wt agent <branch> <plan_prompt>` — the standout
29
45
 
30
46
  ```bash
31
- # Inside any git repo
32
- wt # Browse worktrees
33
- wt create my-feat # Create a new worktree and open it in your IDE
34
- wt agent my-feat 'Plan the feature' # Create + auto-start an AI agent in Zed (macOS)
35
- wt config # Edit config in $EDITOR
36
- wt config --path # Print config file path
37
- wt skill # Print skill file (for AI agents)
47
+ wt agent feat/login "Read the codebase, then propose a plan for login."
38
48
  ```
39
49
 
40
- ## Usage
50
+ Creates a worktree exactly like `wt create`, then auto-starts your agent
51
+ (default `claude --permission-mode plan`) in Zed's integrated terminal —
52
+ pre-filled with your prompt and left interactive for you to take over.
53
+
54
+ Under the hood it writes a temporary `.zed/tasks.json`, installs a global Zed
55
+ keymap chord, opens Zed and fires the chord via `osascript`, then removes the
56
+ temp task so the repo stays clean.
41
57
 
42
- ### Browse worktrees `wt`
58
+ **Requires** macOS, Zed, and Accessibility permission for the app running `wt`.
59
+ Not granted yet? `wt agent` opens *System Settings → Privacy & Security →
60
+ Accessibility*, waits while you grant it (you may need to quit and reopen the
61
+ app), then retries automatically. On other platforms — or when `ide` isn't
62
+ `zed` — the worktree is still created and opened, just without the agent.
43
63
 
44
- Launches an interactive list of worktrees with fuzzy search.
64
+ If the path already exists, `wt agent` offers to open it — or open it and start
65
+ the agent — instead of erroring (in a non-interactive shell it exits non-zero).
66
+
67
+ > **Tip:** trust the parent directory of your worktrees in Claude once, and
68
+ > every worktree created beneath it starts hands-free.
69
+
70
+ ## Browse — `wt`
71
+
72
+ An interactive, fuzzy-searchable list of your worktrees:
45
73
 
46
74
  ```
47
75
  MY-PROJECT
48
- ▶ main ~/dev/my-project
76
+ ▶ main ~/dev/my-project
49
77
  fix: resolve auth bug (2h ago)
50
78
  feat/dashboard ~/dev/my-project-feat-dashboard
51
79
  wip: add chart component (1d ago)
@@ -53,89 +81,46 @@ MY-PROJECT
53
81
  ↕ navigate · Enter open · D delete · C create · Q quit
54
82
  ```
55
83
 
56
- | Key | Action |
57
- | ------- | ------------------------------- |
58
- | `↑` `↓` | Navigate |
59
- | `Enter` | Open worktree in IDE |
60
- | `D` | Delete worktree (with confirm) |
61
- | `C` | Create new worktree (repo mode) |
62
- | `Q` | Quit |
63
-
64
- Type to fuzzy-filter branches instantly.
84
+ Type to fuzzy-filter branches instantly. Inside a repo it shows that repo's
85
+ worktrees (and `C` creates one); run it outside any repo to browse worktrees
86
+ across all registered repos.
65
87
 
66
- **Repo mode** (inside a git repo): shows worktrees for that repo.
67
- **Global mode** (outside a repo): shows worktrees across all registered repos.
68
-
69
- ### Create a worktree — `wt create [branch]`
88
+ ## Create `wt create [branch]`
70
89
 
71
90
  ```bash
72
- wt create feat/login # Create from base branch (origin/main by default)
73
- wt create # Prompts for branch name
91
+ wt create feat/login # From base branch (origin/main by default)
92
+ wt create # Prompts for a branch name
74
93
  ```
75
94
 
76
- What happens:
77
-
78
- 1. Creates a worktree as a sibling directory: `../my-project-feat-login`
79
- 2. Runs configured setup commands (e.g., `npm install`)
80
- 3. Opens the worktree in your IDE
95
+ Creates a worktree as a sibling directory (`../my-project-feat-login`), runs your
96
+ `setup_commands`, and opens it in your IDE. Run it outside a repo to pick from
97
+ registered repos.
81
98
 
82
- Run outside a repo to pick from registered repos via an interactive picker.
99
+ If the path already exists, `wt create` offers to open it in your IDE instead of
100
+ erroring (in a non-interactive shell it exits non-zero).
83
101
 
84
- ### Start an AI agent — `wt agent <branch> <plan_prompt>` (macOS + Zed)
85
-
86
- ```bash
87
- wt agent feat/login 'Read the codebase, then propose a plan for login.'
88
- ```
89
-
90
- Creates the worktree exactly like `wt create`, then auto-starts an AI agent
91
- (default `claude --permission-mode plan`) in Zed's integrated terminal,
92
- pre-filled with your prompt and left interactive for you to take over.
93
-
94
- How it works:
95
-
96
- 1. Writes a temporary `.zed/tasks.json` running `<agent_command> '<plan_prompt>'`.
97
- 2. Ensures a global Zed keymap chord (`agent_trigger_chord`) spawns that task.
98
- 3. Opens Zed, then presses the chord via `osascript`.
99
- 4. Removes the temporary task afterwards, leaving the repo clean.
100
-
101
- **Requirements:** macOS, Zed, and **Accessibility** permission for the app that
102
- runs `wt` (Zed itself, when run from its integrated terminal). If it isn't
103
- granted yet, `wt agent` detects this, opens *System Settings → Privacy &
104
- Security → Accessibility* for you, and waits — grant it (you may need to quit and
105
- reopen the app), confirm, and `wt` retries automatically. On other platforms (or
106
- when `ide` is not `zed`), the worktree is still created and opened, but the agent
107
- is not auto-started.
108
-
109
- > Tip: trust the parent directory of your worktrees in Claude once (open it and
110
- > accept the trust prompt) so every worktree created beneath it is trusted
111
- > automatically and the agent starts hands-free.
112
-
113
- ### Edit config — `wt config`
114
-
115
- Opens the config file in `$EDITOR`.
116
-
117
- ```bash
118
- wt config # Open in editor
119
- wt config --path # Print the config file path only
120
- ```
121
-
122
- ### Print skill file — `wt skill`
102
+ ## Configuration
123
103
 
124
- Prints the bundled skill documentation to stdout. Useful for piping to AI agents or copying into a project.
104
+ Edit with `wt config` (`wt config --path` prints the file location
105
+ `~/Library/Preferences/wt-nodejs/config.json` on macOS).
125
106
 
126
- ## Configuration
107
+ | Key | Default | Description |
108
+ | --------------------- | --------------------------------- | ------------------------------------------------- |
109
+ | `ide` | `"zed"` | Editor to open worktrees with |
110
+ | `ide_open_args` | `["-n"]` | Extra args passed to the IDE command |
111
+ | `base_branch` | `"origin/main"` | Branch new worktrees are created from |
112
+ | `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
113
+ | `setup_commands` | `[]` | Commands to run in new worktrees |
114
+ | `agent_command` | `"claude --permission-mode plan"` | What `wt agent` runs in Zed (prompt appended) |
115
+ | `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs and presses |
116
+ | `repo_overrides` | `{}` | Per-repo overrides for any key above |
127
117
 
128
- Config is stored at `~/Library/Preferences/wt-nodejs/config.json` (macOS).
118
+ Override any key per repo:
129
119
 
130
120
  ```json
131
121
  {
132
- "ide": "code",
133
- "ide_open_args": ["-n"],
134
- "base_branch": "origin/main",
135
- "worktree_path": "../",
136
- "setup_commands": ["npm install"],
137
122
  "repo_overrides": {
138
- "/path/to/special-repo": {
123
+ "/path/to/repo": {
139
124
  "base_branch": "origin/develop",
140
125
  "setup_commands": ["pnpm install", "pnpm build"]
141
126
  }
@@ -143,16 +128,12 @@ Config is stored at `~/Library/Preferences/wt-nodejs/config.json` (macOS).
143
128
  }
144
129
  ```
145
130
 
146
- | Key | Default | Description |
147
- | ---------------- | --------------- | --------------------------------------------- |
148
- | `ide` | `"zed"` | Editor to open worktrees with |
149
- | `ide_open_args` | `["-n"]` | Extra args passed to the IDE command |
150
- | `base_branch` | `"origin/main"` | Branch new worktrees are created from |
151
- | `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
152
- | `setup_commands` | `[]` | Commands to run in new worktrees |
153
- | `agent_command` | `"claude --permission-mode plan"` | Command `wt agent` runs in Zed (prompt appended) |
154
- | `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses |
155
- | `repo_overrides` | `{}` | Per-repo overrides for any of the above |
131
+ ## Pre-release builds
132
+
133
+ Add the `publish-dev` label to a PR to publish that branch as a unique, pinned
134
+ prerelease (e.g. `0.1.0-pr12.gabc1234`); the exact install command is posted as a
135
+ PR comment. There's no rolling `dev` channel each build is a distinct version
136
+ you install explicitly.
156
137
 
157
138
  ## License
158
139
 
package/SKILL.md CHANGED
@@ -30,6 +30,10 @@ The worktree is created as a sibling directory to the repo: `<parent>/<repo-name
30
30
 
31
31
  After creation, `wt` runs any configured `setup_commands` and opens the worktree in your IDE.
32
32
 
33
+ If the worktree path already exists, `wt create` doesn't error — it prompts you
34
+ to **open it in the IDE** or **quit**. (In a non-interactive shell it errors
35
+ with a non-zero exit instead of prompting.)
36
+
33
37
  ### `wt agent <branch> <plan_prompt>`
34
38
 
35
39
  Create a worktree (same as `wt create`) **and** auto-start an AI agent in Zed's
@@ -52,6 +56,10 @@ for you to grant it and confirm, then retries automatically. On other platforms
52
56
  (or when `ide` is not `zed`) the worktree is still created and opened, but the
53
57
  agent is not auto-started.
54
58
 
59
+ If the worktree path already exists, `wt agent` prompts you to **open it in the
60
+ IDE**, **open it and start the agent**, or **quit** — instead of erroring. (In a
61
+ non-interactive shell it errors with a non-zero exit instead of prompting.)
62
+
55
63
  ### `wt config`
56
64
 
57
65
  Open the global config file in `$EDITOR` (defaults to `nano`).
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- prepareWorktree
4
- } from "./chunk-D4BVSTNE.js";
5
- import {
6
- openIde
7
- } from "./chunk-FSARWOCW.js";
3
+ openConfiguredIde,
4
+ prepareWorktree,
5
+ promptExistingWorktree
6
+ } from "./chunk-BYWCGKUP.js";
7
+ import "./chunk-FSARWOCW.js";
8
8
  import "./chunk-IQWENLPW.js";
9
9
 
10
10
  // src/commands/agent.ts
@@ -14,7 +14,6 @@ import pc from "picocolors";
14
14
  // src/lib/zed.ts
15
15
  import { spawn } from "child_process";
16
16
  import {
17
- copyFileSync,
18
17
  existsSync,
19
18
  mkdirSync,
20
19
  readdirSync,
@@ -39,13 +38,6 @@ function buildAgentTask(agentCommand, prompt, label) {
39
38
  shell: "system"
40
39
  };
41
40
  }
42
- function upsertTask(tasks, task) {
43
- const idx = tasks.findIndex((t) => t.label === task.label);
44
- if (idx === -1) return [...tasks, task];
45
- const next = [...tasks];
46
- next[idx] = task;
47
- return next;
48
- }
49
41
  function removeTask(tasks, label) {
50
42
  return tasks.filter((t) => t.label !== label);
51
43
  }
@@ -64,6 +56,12 @@ function upsertKeymapBinding(keymap, chord, label) {
64
56
  if (already) return keymap;
65
57
  return [...keymap, target];
66
58
  }
59
+ function hasConflictingChord(keymap, chord, label) {
60
+ const ours = JSON.stringify(buildKeymapBinding(chord, label).bindings[chord]);
61
+ return keymap.some(
62
+ (e) => e.bindings != null && e.bindings[chord] !== void 0 && JSON.stringify(e.bindings[chord]) !== ours
63
+ );
64
+ }
67
65
  var MODIFIER_MAP = {
68
66
  ctrl: "control down",
69
67
  control: "control down",
@@ -75,6 +73,37 @@ var MODIFIER_MAP = {
75
73
  opt: "option down",
76
74
  option: "option down"
77
75
  };
76
+ var KEY_CODE_MAP = {
77
+ space: 49,
78
+ tab: 48,
79
+ return: 36,
80
+ enter: 36,
81
+ escape: 53,
82
+ esc: 53,
83
+ delete: 51,
84
+ backspace: 51,
85
+ forwarddelete: 117,
86
+ up: 126,
87
+ down: 125,
88
+ left: 123,
89
+ right: 124,
90
+ home: 115,
91
+ end: 119,
92
+ pageup: 116,
93
+ pagedown: 121,
94
+ f1: 122,
95
+ f2: 120,
96
+ f3: 99,
97
+ f4: 118,
98
+ f5: 96,
99
+ f6: 97,
100
+ f7: 98,
101
+ f8: 100,
102
+ f9: 101,
103
+ f10: 109,
104
+ f11: 103,
105
+ f12: 111
106
+ };
78
107
  function parseChord(chord) {
79
108
  const parts = chord.split("-").map((p) => p.trim().toLowerCase()).filter(Boolean);
80
109
  if (parts.length === 0) {
@@ -94,17 +123,35 @@ function buildOsascript(chord, opts = {}) {
94
123
  const { loadDelay = 3, activateDelay = 0.8 } = opts;
95
124
  const { key, modifiers } = parseChord(chord);
96
125
  const using = modifiers.length > 0 ? ` using {${modifiers.join(", ")}}` : "";
126
+ let press;
127
+ if (key.length === 1) {
128
+ const escaped = key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
129
+ press = `keystroke "${escaped}"`;
130
+ } else {
131
+ const code = KEY_CODE_MAP[key];
132
+ if (code === void 0) {
133
+ throw new Error(
134
+ `Unsupported key "${key}" in chord "${chord}". Use a single character or one of: ${Object.keys(KEY_CODE_MAP).join(", ")}.`
135
+ );
136
+ }
137
+ press = `key code ${code}`;
138
+ }
97
139
  return [
98
140
  `delay ${loadDelay}`,
99
141
  'tell application "Zed" to activate',
100
142
  `delay ${activateDelay}`,
101
- `tell application "System Events" to keystroke "${key}"${using}`
143
+ `tell application "System Events" to ${press}${using}`
102
144
  ].join("\n");
103
145
  }
146
+ function parseTasks(raw) {
147
+ const errors = [];
148
+ const parsed = parse(raw, errors, { allowTrailingComma: true });
149
+ if (errors.length > 0 || !Array.isArray(parsed)) return null;
150
+ return parsed;
151
+ }
104
152
  function readTasks(tasksPath) {
105
153
  try {
106
- const parsed = JSON.parse(readFileSync(tasksPath, "utf8"));
107
- return Array.isArray(parsed) ? parsed : [];
154
+ return parseTasks(readFileSync(tasksPath, "utf8")) ?? [];
108
155
  } catch {
109
156
  return [];
110
157
  }
@@ -115,9 +162,28 @@ function writeAgentTask(worktreePath, task) {
115
162
  const createdDir = !existsSync(zedDir);
116
163
  if (createdDir) mkdirSync(zedDir, { recursive: true });
117
164
  const createdFile = !existsSync(tasksPath);
118
- const existing = createdFile ? [] : readTasks(tasksPath);
119
- const next = upsertTask(existing, task);
120
- writeFileSync(tasksPath, `${JSON.stringify(next, null, 2)}
165
+ if (createdFile) {
166
+ writeFileSync(tasksPath, `${JSON.stringify([task], null, 2)}
167
+ `);
168
+ return { createdDir, createdFile };
169
+ }
170
+ const raw = readFileSync(tasksPath, "utf8");
171
+ const tasks = parseTasks(raw);
172
+ if (tasks === null) {
173
+ process.stderr.write(
174
+ `
175
+ Warning: could not parse ${tasksPath}; not modifying it. The agent task was not added.
176
+ `
177
+ );
178
+ return { createdDir, createdFile };
179
+ }
180
+ const idx = tasks.findIndex((t) => t.label === task.label);
181
+ const edits = modify(raw, idx === -1 ? [tasks.length] : [idx], task, {
182
+ isArrayInsertion: idx === -1,
183
+ formattingOptions: { insertSpaces: true, tabSize: 2 }
184
+ });
185
+ const updated = applyEdits(raw, edits);
186
+ writeFileSync(tasksPath, updated.endsWith("\n") ? updated : `${updated}
121
187
  `);
122
188
  return { createdDir, createdFile };
123
189
  }
@@ -125,12 +191,30 @@ function cleanupAgentTask(worktreePath, label, created) {
125
191
  const zedDir = join(worktreePath, ".zed");
126
192
  const tasksPath = join(zedDir, "tasks.json");
127
193
  if (existsSync(tasksPath)) {
128
- const next = removeTask(readTasks(tasksPath), label);
129
- if (next.length === 0 && created.createdFile) {
130
- rmSync(tasksPath, { force: true });
131
- } else {
132
- writeFileSync(tasksPath, `${JSON.stringify(next, null, 2)}
194
+ try {
195
+ if (created.createdFile) {
196
+ const next = removeTask(readTasks(tasksPath), label);
197
+ if (next.length === 0) {
198
+ rmSync(tasksPath, { force: true });
199
+ } else {
200
+ writeFileSync(tasksPath, `${JSON.stringify(next, null, 2)}
133
201
  `);
202
+ }
203
+ } else {
204
+ const raw = readFileSync(tasksPath, "utf8");
205
+ const tasks = parseTasks(raw);
206
+ const idx = tasks?.findIndex((t) => t.label === label) ?? -1;
207
+ if (idx !== -1) {
208
+ const edits = modify(raw, [idx], void 0, {});
209
+ const updated = applyEdits(raw, edits);
210
+ writeFileSync(
211
+ tasksPath,
212
+ updated.endsWith("\n") ? updated : `${updated}
213
+ `
214
+ );
215
+ }
216
+ }
217
+ } catch {
134
218
  }
135
219
  }
136
220
  if (created.createdDir && existsSync(zedDir)) {
@@ -172,12 +256,18 @@ ${JSON.stringify(binding, null, 2)}
172
256
  if (upsertKeymapBinding(keymap, chord, label) === keymap) {
173
257
  return true;
174
258
  }
259
+ if (hasConflictingChord(keymap, chord, label)) {
260
+ process.stderr.write(
261
+ `
262
+ Note: "${chord}" is already bound in ${keymapPath}; wt's agent binding will take precedence.
263
+ `
264
+ );
265
+ }
175
266
  const edits = modify(raw, [keymap.length], binding, {
176
267
  isArrayInsertion: true,
177
268
  formattingOptions: { insertSpaces: true, tabSize: 2 }
178
269
  });
179
270
  const updated = applyEdits(raw, edits);
180
- copyFileSync(keymapPath, `${keymapPath}.bak`);
181
271
  writeFileSync(keymapPath, updated.endsWith("\n") ? updated : `${updated}
182
272
  `);
183
273
  return true;
@@ -229,27 +319,38 @@ function openAccessibilitySettings(open = defaultOpen) {
229
319
  }
230
320
 
231
321
  // src/commands/agent.ts
232
- var CLEANUP_DELAY_MS = 2e3;
322
+ var CLEANUP_DELAY_MS = 2e4;
233
323
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
234
324
  async function createAgentWorktree(branch, planPrompt, options = {}) {
235
325
  const prepared = await prepareWorktree(branch, options);
236
326
  if (!prepared) return;
237
- const { config, worktreePath } = prepared;
327
+ const { status, config, worktreePath } = prepared;
328
+ if (status === "exists") {
329
+ const prompt = options.existingWorktreePrompt ?? promptExistingWorktree;
330
+ const action = await prompt(worktreePath, { allowAgent: true });
331
+ if (action === "quit") return;
332
+ if (action === "open") {
333
+ await openConfiguredIde(config, worktreePath);
334
+ return;
335
+ }
336
+ }
337
+ await startAgentInWorktree(config, worktreePath, planPrompt);
338
+ }
339
+ async function startAgentInWorktree(config, worktreePath, planPrompt) {
238
340
  if (config.ide !== "zed") {
239
341
  console.warn(
240
342
  pc.yellow(
241
343
  `\u26A0 Agent auto-start requires Zed (ide is "${config.ide}"). Opening without starting the agent.`
242
344
  )
243
345
  );
244
- if (config.ide) {
245
- await openIde(config.ide, config.ide_open_args, worktreePath);
246
- }
346
+ await openConfiguredIde(config, worktreePath);
247
347
  return;
248
348
  }
249
349
  if (!config.agent_command) {
250
350
  console.error(
251
351
  pc.red("No agent_command configured. Set it with `wt config`.")
252
352
  );
353
+ await openConfiguredIde(config, worktreePath);
253
354
  return;
254
355
  }
255
356
  const task = buildAgentTask(
@@ -258,14 +359,21 @@ async function createAgentWorktree(branch, planPrompt, options = {}) {
258
359
  AGENT_TASK_LABEL
259
360
  );
260
361
  const created = writeAgentTask(worktreePath, task);
261
- ensureKeymap(config.agent_trigger_chord, AGENT_TASK_LABEL);
262
- const opened = await openIde("zed", config.ide_open_args, worktreePath);
362
+ const keymapOk = ensureKeymap(config.agent_trigger_chord, AGENT_TASK_LABEL);
363
+ const opened = await openConfiguredIde(config, worktreePath);
263
364
  if (!opened) {
264
365
  console.error(pc.red("\u2717 Could not open Zed."));
265
366
  cleanupAgentTask(worktreePath, AGENT_TASK_LABEL, created);
266
367
  return;
267
368
  }
268
- console.log(pc.green("\u2713 Opened Zed"));
369
+ if (!keymapOk) {
370
+ console.warn(
371
+ pc.yellow(
372
+ `\u26A0 Could not install the Zed keybinding (see above). In Zed, press ${config.agent_trigger_chord} to start the agent manually.`
373
+ )
374
+ );
375
+ return;
376
+ }
269
377
  console.log(pc.dim("Starting agent in Zed\u2026"));
270
378
  let result = await triggerChord(config.agent_trigger_chord);
271
379
  while (!result.ok && result.reason === "accessibility" && process.stdin.isTTY) {
@@ -5,6 +5,7 @@ import {
5
5
  fetchRemote,
6
6
  getRegisteredRepos,
7
7
  getRepoRoot,
8
+ listWorktrees,
8
9
  openIde,
9
10
  registerRepo,
10
11
  resolveWorktreePath,
@@ -97,6 +98,25 @@ async function prepareWorktree(branch, options = {}) {
97
98
  }
98
99
  registerRepo(repoRoot, store);
99
100
  const config = getEffectiveConfig(repoRoot, store);
101
+ const worktreePath = resolveWorktreePath(
102
+ repoRoot,
103
+ config.worktree_path,
104
+ branch
105
+ );
106
+ if (existsSync(worktreePath)) {
107
+ const isWorktree = listWorktrees(repoRoot).some(
108
+ (wt) => wt.path === worktreePath
109
+ );
110
+ if (!isWorktree) {
111
+ console.error(
112
+ pc.red(
113
+ `Path already exists but is not a git worktree: ${worktreePath}`
114
+ )
115
+ );
116
+ process.exit(1);
117
+ }
118
+ return { status: "exists", repoRoot, worktreePath, config };
119
+ }
100
120
  const parts = config.base_branch.split("/", 2);
101
121
  if (parts.length === 2) {
102
122
  const remote = parts[0];
@@ -110,14 +130,6 @@ async function prepareWorktree(branch, options = {}) {
110
130
  );
111
131
  }
112
132
  }
113
- const worktreePath = resolveWorktreePath(
114
- repoRoot,
115
- config.worktree_path,
116
- branch
117
- );
118
- if (existsSync(worktreePath)) {
119
- throw new Error(`Worktree path already exists: ${worktreePath}`);
120
- }
121
133
  const exists = branchExists(repoRoot, branch);
122
134
  if (exists) {
123
135
  addWorktree(repoRoot, worktreePath, branch);
@@ -138,25 +150,47 @@ async function prepareWorktree(branch, options = {}) {
138
150
  process.exit(1);
139
151
  }
140
152
  }
141
- return { repoRoot, worktreePath, config };
153
+ return { status: "created", repoRoot, worktreePath, config };
154
+ }
155
+ async function promptExistingWorktree(worktreePath, opts) {
156
+ if (!process.stdin.isTTY) {
157
+ console.error(pc.red(`Worktree path already exists: ${worktreePath}`));
158
+ process.exit(1);
159
+ }
160
+ const choice = await clack.select({
161
+ message: `Worktree already exists at ${worktreePath}.`,
162
+ options: [
163
+ { value: "open", label: "Open in IDE" },
164
+ ...opts.allowAgent ? [{ value: "agent", label: "Open and start agent" }] : [],
165
+ { value: "quit", label: "Ignore and quit" }
166
+ ]
167
+ });
168
+ if (clack.isCancel(choice)) return "quit";
169
+ return choice;
170
+ }
171
+ async function openConfiguredIde(config, worktreePath) {
172
+ if (!config.ide) return false;
173
+ const opened = await openIde(config.ide, config.ide_open_args, worktreePath);
174
+ if (opened) {
175
+ console.log(pc.green(`\u2713 Opened ${config.ide}`));
176
+ }
177
+ return opened;
142
178
  }
143
179
  async function createWorktree(branch, options = {}) {
144
180
  const prepared = await prepareWorktree(branch, options);
145
181
  if (!prepared) return;
146
- const { config, worktreePath } = prepared;
147
- if (config.ide) {
148
- const opened = await openIde(
149
- config.ide,
150
- config.ide_open_args,
151
- worktreePath
152
- );
153
- if (opened) {
154
- console.log(pc.green(`\u2713 Opened ${config.ide}`));
155
- }
182
+ const { status, config, worktreePath } = prepared;
183
+ if (status === "exists") {
184
+ const prompt = options.existingWorktreePrompt ?? promptExistingWorktree;
185
+ const action = await prompt(worktreePath, { allowAgent: false });
186
+ if (action !== "open") return;
156
187
  }
188
+ await openConfiguredIde(config, worktreePath);
157
189
  }
158
190
 
159
191
  export {
160
192
  prepareWorktree,
193
+ promptExistingWorktree,
194
+ openConfiguredIde,
161
195
  createWorktree
162
196
  };
package/dist/cli.js CHANGED
@@ -3,16 +3,16 @@
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.1.0-pr5.gd63269d").action(async () => {
7
- const { runList } = await import("./list-HO42YAEG.js");
6
+ program.name("wt").description("Git worktree manager").version("0.2.0-pr9.g997ac8d").action(async () => {
7
+ const { runList } = await import("./list-5TXRTSIW.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-55SXEUZV.js");
11
+ const { createWorktree } = await import("./create-5J5NGGGM.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)").action(async (branch, planPrompt) => {
15
- const { createAgentWorktree } = await import("./agent-PR7PGIE5.js");
15
+ const { createAgentWorktree } = await import("./agent-PKCZHGN7.js");
16
16
  await createAgentWorktree(branch, planPrompt);
17
17
  });
18
18
  program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
@@ -25,7 +25,7 @@ program.command("config").description("Open the config file in $EDITOR").option(
25
25
  }
26
26
  });
27
27
  program.command("skill").description("Print the wt skill file to stdout").action(async () => {
28
- const { printSkill } = await import("./skill-CRAP2ALY.js");
28
+ const { printSkill } = await import("./skill-RR6MNLWH.js");
29
29
  printSkill();
30
30
  });
31
31
  await program.parseAsync(process.argv);
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createWorktree,
4
+ openConfiguredIde,
5
+ prepareWorktree,
6
+ promptExistingWorktree
7
+ } from "./chunk-BYWCGKUP.js";
8
+ import "./chunk-FSARWOCW.js";
9
+ import "./chunk-IQWENLPW.js";
10
+ export {
11
+ createWorktree,
12
+ openConfiguredIde,
13
+ prepareWorktree,
14
+ promptExistingWorktree
15
+ };
@@ -66,6 +66,23 @@ async function runList(options = {}) {
66
66
  return true;
67
67
  } catch (err) {
68
68
  const msg = String(err);
69
+ if (msg.includes("cannot be moved or removed")) {
70
+ clack.log.warn(
71
+ "Worktree contains git submodules, which prevent standard removal."
72
+ );
73
+ const force = await clack.confirm({
74
+ message: `Force delete ${pc.bold(item.branch)}? The worktree directory will be removed directly.`
75
+ });
76
+ if (clack.isCancel(force) || !force) return false;
77
+ try {
78
+ removeWorktree(item.repoRoot, item.path, true);
79
+ console.log(pc.green(`\u2713 Force-removed ${item.branch}`));
80
+ return true;
81
+ } catch (err2) {
82
+ console.error(pc.red(`\u2717 Failed to force-remove: ${String(err2)}`));
83
+ return false;
84
+ }
85
+ }
69
86
  if (msg.includes("modified or untracked files")) {
70
87
  const dirty = listWorktreeDirtyFiles(item.path);
71
88
  if (dirty.length > 0) {
@@ -93,7 +110,7 @@ ${dirty.map((f) => ` ${f}`).join("\n")}`
93
110
  },
94
111
  onCreate: async () => {
95
112
  if (repoRoot) {
96
- const { createWorktree } = await import("./create-55SXEUZV.js");
113
+ const { createWorktree } = await import("./create-5J5NGGGM.js");
97
114
  await createWorktree(void 0, { cwd: repoRoot, store });
98
115
  }
99
116
  }
@@ -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 / `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>`\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.\'\n```\n\nIt writes a temporary `.zed/tasks.json` running\n`<agent_command> \'<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\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| `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"` | Command `wt agent` runs in Zed; `<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`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:\n\n```json\n{\n "base_branch": "origin/main",\n "ide": "zed",\n "repo_overrides": {\n "/path/to/my-repo": {\n "base_branch": "origin/develop",\n "setup_commands": ["npm install", "npm run build"]\n }\n }\n}\n```\n\n## Common workflows\n\n### Create a worktree for a new feature\n\n```bash\ncd /path/to/repo\nwt create feature/my-branch\n```\n\n### Configure setup commands for a repo\n\n```bash\nwt config\n# Then add to the JSON:\n# "repo_overrides": {\n# "/path/to/repo": {\n# "setup_commands": ["npm install"]\n# }\n# }\n```\n\n### Browse all worktrees across repos\n\nRun `wt` from any directory outside a git repo to see worktrees from all registered repos.\n');
6
+ }
7
+ export {
8
+ printSkill
9
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cestoliv/wt",
3
- "version": "0.1.0-pr5.gd63269d",
3
+ "version": "0.2.0-pr9.g997ac8d",
4
4
  "description": "Fast, interactive TUI to browse, create, open, and delete git worktrees across repos.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,11 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- createWorktree,
4
- prepareWorktree
5
- } from "./chunk-D4BVSTNE.js";
6
- import "./chunk-FSARWOCW.js";
7
- import "./chunk-IQWENLPW.js";
8
- export {
9
- createWorktree,
10
- prepareWorktree
11
- };
@@ -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\n### `wt agent <branch> <plan_prompt>`\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.\'\n```\n\nIt writes a temporary `.zed/tasks.json` running\n`<agent_command> \'<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\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| `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"` | Command `wt agent` runs in Zed; `<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`, `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
- };