@cestoliv/wt 0.1.0-pr1.f956696 → 0.1.0-pr5.g3ca944a

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
@@ -20,12 +20,10 @@ npm install -g @cestoliv/wt
20
20
 
21
21
  ### Pre-release builds
22
22
 
23
- Add the `publish-dev` label to a PR to publish that branch under the `dev`
24
- dist-tag (the label is removed automatically afterwards):
25
-
26
- ```bash
27
- npm install -g @cestoliv/wt@dev
28
- ```
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.
29
27
 
30
28
  ## Quick Start
31
29
 
@@ -33,7 +31,10 @@ npm install -g @cestoliv/wt@dev
33
31
  # Inside any git repo
34
32
  wt # Browse worktrees
35
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)
36
35
  wt config # Edit config in $EDITOR
36
+ wt config --path # Print config file path
37
+ wt skill # Print skill file (for AI agents)
37
38
  ```
38
39
 
39
40
  ## Usage
@@ -80,10 +81,48 @@ What happens:
80
81
 
81
82
  Run outside a repo to pick from registered repos via an interactive picker.
82
83
 
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
+
83
113
  ### Edit config — `wt config`
84
114
 
85
115
  Opens the config file in `$EDITOR`.
86
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`
123
+
124
+ Prints the bundled skill documentation to stdout. Useful for piping to AI agents or copying into a project.
125
+
87
126
  ## Configuration
88
127
 
89
128
  Config is stored at `~/Library/Preferences/wt-nodejs/config.json` (macOS).
@@ -111,6 +150,8 @@ Config is stored at `~/Library/Preferences/wt-nodejs/config.json` (macOS).
111
150
  | `base_branch` | `"origin/main"` | Branch new worktrees are created from |
112
151
  | `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
113
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 |
114
155
  | `repo_overrides` | `{}` | Per-repo overrides for any of the above |
115
156
 
116
157
  ## License
package/SKILL.md ADDED
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: wt-worktree-manager
3
+ description: 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.
4
+ ---
5
+
6
+ # wt — Git Worktree Manager
7
+
8
+ `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.
9
+
10
+ ## Commands
11
+
12
+ ### `wt` (no subcommand)
13
+
14
+ Launch the interactive TUI. Shows worktrees for the current repo (repo mode) or all registered repos (global mode, when run outside a repo).
15
+
16
+ **Keybindings in the TUI:**
17
+
18
+ - Arrow keys / `j`/`k` — navigate
19
+ - `Enter` — open worktree in IDE
20
+ - `d` — delete worktree
21
+ - `c` — create new worktree (repo mode only)
22
+ - `/` — search
23
+ - `q` / `Esc` — quit
24
+
25
+ ### `wt create [branch]`
26
+
27
+ Create a new worktree. If `branch` is omitted, prompts interactively.
28
+
29
+ The worktree is created as a sibling directory to the repo: `<parent>/<repo-name>-<branch-name>`.
30
+
31
+ After creation, `wt` runs any configured `setup_commands` and opens the worktree in your IDE.
32
+
33
+ ### `wt agent <branch> <plan_prompt>`
34
+
35
+ Create a worktree (same as `wt create`) **and** auto-start an AI agent in Zed's
36
+ integrated terminal, pre-filled with `<plan_prompt>` and left interactive for
37
+ you to take over.
38
+
39
+ ```bash
40
+ wt agent feature/login 'Read the codebase, then propose a plan for login.'
41
+ ```
42
+
43
+ It writes a temporary `.zed/tasks.json` running
44
+ `<agent_command> '<plan_prompt>'`, ensures a global Zed keymap chord
45
+ (`agent_trigger_chord`) spawns that task, opens Zed, presses the chord via
46
+ `osascript`, then removes the temporary task so the repo is left clean.
47
+
48
+ **macOS + Zed only.** Requires Accessibility permission for the app that runs
49
+ `wt` (Zed itself, when run from its integrated terminal). If it isn't granted,
50
+ `wt agent` opens the *Privacy & Security → Accessibility* settings pane and waits
51
+ for you to grant it and confirm, then retries automatically. On other platforms
52
+ (or when `ide` is not `zed`) the worktree is still created and opened, but the
53
+ agent is not auto-started.
54
+
55
+ ### `wt config`
56
+
57
+ Open the global config file in `$EDITOR` (defaults to `nano`).
58
+
59
+ ```bash
60
+ wt config # open in editor
61
+ wt config --path # print config file path only
62
+ ```
63
+
64
+ ### `wt skill`
65
+
66
+ Print this skill file to stdout. Useful for piping to agents or copying to a project.
67
+
68
+ ## Configuration
69
+
70
+ Config is stored as JSON. Get the path with `wt config --path`.
71
+
72
+ ### Schema
73
+
74
+ | Key | Type | Default | Description |
75
+ | ---------------- | ---------- | --------------- | ------------------------------------------------------------------------- |
76
+ | `worktree_path` | `string` | `"../"` | Where to place new worktrees, relative to the repo root |
77
+ | `base_branch` | `string` | `"origin/main"` | Branch to base new worktrees on |
78
+ | `setup_commands` | `string[]` | `[]` | Commands to run in a new worktree after creation (e.g. `["npm install"]`) |
79
+ | `ide` | `string` | `"zed"` | IDE command to open worktrees with |
80
+ | `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |
81
+ | `agent_command` | `string` | `"claude --permission-mode plan"` | Command `wt agent` runs in Zed; `<plan_prompt>` is appended single-quoted |
82
+ | `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |
83
+ | `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |
84
+ | `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |
85
+
86
+ ### Per-repo overrides
87
+
88
+ Override any field (`worktree_path`, `base_branch`, `setup_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:
89
+
90
+ ```json
91
+ {
92
+ "base_branch": "origin/main",
93
+ "ide": "zed",
94
+ "repo_overrides": {
95
+ "/path/to/my-repo": {
96
+ "base_branch": "origin/develop",
97
+ "setup_commands": ["npm install", "npm run build"]
98
+ }
99
+ }
100
+ }
101
+ ```
102
+
103
+ ## Common workflows
104
+
105
+ ### Create a worktree for a new feature
106
+
107
+ ```bash
108
+ cd /path/to/repo
109
+ wt create feature/my-branch
110
+ ```
111
+
112
+ ### Configure setup commands for a repo
113
+
114
+ ```bash
115
+ wt config
116
+ # Then add to the JSON:
117
+ # "repo_overrides": {
118
+ # "/path/to/repo": {
119
+ # "setup_commands": ["npm install"]
120
+ # }
121
+ # }
122
+ ```
123
+
124
+ ### Browse all worktrees across repos
125
+
126
+ Run `wt` from any directory outside a git repo to see worktrees from all registered repos.
@@ -0,0 +1,415 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ openConfiguredIde,
4
+ prepareWorktree
5
+ } from "./chunk-YNGWUXCX.js";
6
+ import "./chunk-FSARWOCW.js";
7
+ import "./chunk-IQWENLPW.js";
8
+
9
+ // src/commands/agent.ts
10
+ import * as clack from "@clack/prompts";
11
+ import pc from "picocolors";
12
+
13
+ // src/lib/zed.ts
14
+ import { spawn } from "child_process";
15
+ import {
16
+ existsSync,
17
+ mkdirSync,
18
+ readdirSync,
19
+ readFileSync,
20
+ rmSync,
21
+ writeFileSync
22
+ } from "fs";
23
+ import { homedir } from "os";
24
+ import { dirname, join } from "path";
25
+ import { applyEdits, modify, parse } from "jsonc-parser";
26
+ var AGENT_TASK_LABEL = "wt: agent";
27
+ function buildAgentTask(agentCommand, prompt, label) {
28
+ const escaped = prompt.replace(/'/g, "'\\''");
29
+ return {
30
+ label,
31
+ command: `${agentCommand} '${escaped}'`,
32
+ cwd: "$ZED_WORKTREE_ROOT",
33
+ use_new_terminal: true,
34
+ allow_concurrent_runs: false,
35
+ reveal: "always",
36
+ reveal_target: "dock",
37
+ shell: "system"
38
+ };
39
+ }
40
+ function removeTask(tasks, label) {
41
+ return tasks.filter((t) => t.label !== label);
42
+ }
43
+ function buildKeymapBinding(chord, label) {
44
+ return {
45
+ context: "Workspace",
46
+ bindings: { [chord]: ["task::Spawn", { task_name: label }] }
47
+ };
48
+ }
49
+ function upsertKeymapBinding(keymap, chord, label) {
50
+ const target = buildKeymapBinding(chord, label);
51
+ const targetValue = JSON.stringify(target.bindings[chord]);
52
+ const already = keymap.some(
53
+ (e) => e.context === "Workspace" && e.bindings != null && JSON.stringify(e.bindings[chord]) === targetValue
54
+ );
55
+ if (already) return keymap;
56
+ return [...keymap, target];
57
+ }
58
+ function hasConflictingChord(keymap, chord, label) {
59
+ const ours = JSON.stringify(buildKeymapBinding(chord, label).bindings[chord]);
60
+ return keymap.some(
61
+ (e) => e.bindings != null && e.bindings[chord] !== void 0 && JSON.stringify(e.bindings[chord]) !== ours
62
+ );
63
+ }
64
+ var MODIFIER_MAP = {
65
+ ctrl: "control down",
66
+ control: "control down",
67
+ shift: "shift down",
68
+ cmd: "command down",
69
+ command: "command down",
70
+ super: "command down",
71
+ alt: "option down",
72
+ opt: "option down",
73
+ option: "option down"
74
+ };
75
+ var KEY_CODE_MAP = {
76
+ space: 49,
77
+ tab: 48,
78
+ return: 36,
79
+ enter: 36,
80
+ escape: 53,
81
+ esc: 53,
82
+ delete: 51,
83
+ backspace: 51,
84
+ forwarddelete: 117,
85
+ up: 126,
86
+ down: 125,
87
+ left: 123,
88
+ right: 124,
89
+ home: 115,
90
+ end: 119,
91
+ pageup: 116,
92
+ pagedown: 121,
93
+ f1: 122,
94
+ f2: 120,
95
+ f3: 99,
96
+ f4: 118,
97
+ f5: 96,
98
+ f6: 97,
99
+ f7: 98,
100
+ f8: 100,
101
+ f9: 101,
102
+ f10: 109,
103
+ f11: 103,
104
+ f12: 111
105
+ };
106
+ function parseChord(chord) {
107
+ const parts = chord.split("-").map((p) => p.trim().toLowerCase()).filter(Boolean);
108
+ if (parts.length === 0) {
109
+ throw new Error(`Invalid chord: "${chord}"`);
110
+ }
111
+ const key = parts[parts.length - 1];
112
+ const modifiers = parts.slice(0, -1).map((m) => {
113
+ const mapped = MODIFIER_MAP[m];
114
+ if (!mapped) {
115
+ throw new Error(`Unknown modifier "${m}" in chord "${chord}"`);
116
+ }
117
+ return mapped;
118
+ });
119
+ return { key, modifiers };
120
+ }
121
+ function buildOsascript(chord, opts = {}) {
122
+ const { loadDelay = 3, activateDelay = 0.8 } = opts;
123
+ const { key, modifiers } = parseChord(chord);
124
+ const using = modifiers.length > 0 ? ` using {${modifiers.join(", ")}}` : "";
125
+ let press;
126
+ if (key.length === 1) {
127
+ const escaped = key.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
128
+ press = `keystroke "${escaped}"`;
129
+ } else {
130
+ const code = KEY_CODE_MAP[key];
131
+ if (code === void 0) {
132
+ throw new Error(
133
+ `Unsupported key "${key}" in chord "${chord}". Use a single character or one of: ${Object.keys(KEY_CODE_MAP).join(", ")}.`
134
+ );
135
+ }
136
+ press = `key code ${code}`;
137
+ }
138
+ return [
139
+ `delay ${loadDelay}`,
140
+ 'tell application "Zed" to activate',
141
+ `delay ${activateDelay}`,
142
+ `tell application "System Events" to ${press}${using}`
143
+ ].join("\n");
144
+ }
145
+ function parseTasks(raw) {
146
+ const errors = [];
147
+ const parsed = parse(raw, errors, { allowTrailingComma: true });
148
+ if (errors.length > 0 || !Array.isArray(parsed)) return null;
149
+ return parsed;
150
+ }
151
+ function readTasks(tasksPath) {
152
+ try {
153
+ return parseTasks(readFileSync(tasksPath, "utf8")) ?? [];
154
+ } catch {
155
+ return [];
156
+ }
157
+ }
158
+ function writeAgentTask(worktreePath, task) {
159
+ const zedDir = join(worktreePath, ".zed");
160
+ const tasksPath = join(zedDir, "tasks.json");
161
+ const createdDir = !existsSync(zedDir);
162
+ if (createdDir) mkdirSync(zedDir, { recursive: true });
163
+ const createdFile = !existsSync(tasksPath);
164
+ if (createdFile) {
165
+ writeFileSync(tasksPath, `${JSON.stringify([task], null, 2)}
166
+ `);
167
+ return { createdDir, createdFile };
168
+ }
169
+ const raw = readFileSync(tasksPath, "utf8");
170
+ const tasks = parseTasks(raw);
171
+ if (tasks === null) {
172
+ process.stderr.write(
173
+ `
174
+ Warning: could not parse ${tasksPath}; not modifying it. The agent task was not added.
175
+ `
176
+ );
177
+ return { createdDir, createdFile };
178
+ }
179
+ const idx = tasks.findIndex((t) => t.label === task.label);
180
+ const edits = modify(raw, idx === -1 ? [tasks.length] : [idx], task, {
181
+ isArrayInsertion: idx === -1,
182
+ formattingOptions: { insertSpaces: true, tabSize: 2 }
183
+ });
184
+ const updated = applyEdits(raw, edits);
185
+ writeFileSync(tasksPath, updated.endsWith("\n") ? updated : `${updated}
186
+ `);
187
+ return { createdDir, createdFile };
188
+ }
189
+ function cleanupAgentTask(worktreePath, label, created) {
190
+ const zedDir = join(worktreePath, ".zed");
191
+ const tasksPath = join(zedDir, "tasks.json");
192
+ if (existsSync(tasksPath)) {
193
+ try {
194
+ if (created.createdFile) {
195
+ const next = removeTask(readTasks(tasksPath), label);
196
+ if (next.length === 0) {
197
+ rmSync(tasksPath, { force: true });
198
+ } else {
199
+ writeFileSync(tasksPath, `${JSON.stringify(next, null, 2)}
200
+ `);
201
+ }
202
+ } else {
203
+ const raw = readFileSync(tasksPath, "utf8");
204
+ const tasks = parseTasks(raw);
205
+ const idx = tasks?.findIndex((t) => t.label === label) ?? -1;
206
+ if (idx !== -1) {
207
+ const edits = modify(raw, [idx], void 0, {});
208
+ const updated = applyEdits(raw, edits);
209
+ writeFileSync(
210
+ tasksPath,
211
+ updated.endsWith("\n") ? updated : `${updated}
212
+ `
213
+ );
214
+ }
215
+ }
216
+ } catch {
217
+ }
218
+ }
219
+ if (created.createdDir && existsSync(zedDir)) {
220
+ try {
221
+ if (readdirSync(zedDir).length === 0) {
222
+ rmSync(zedDir, { recursive: true, force: true });
223
+ }
224
+ } catch {
225
+ }
226
+ }
227
+ }
228
+ function defaultKeymapPath() {
229
+ return join(homedir(), ".config", "zed", "keymap.json");
230
+ }
231
+ function ensureKeymap(chord, label, keymapPath = defaultKeymapPath()) {
232
+ const binding = buildKeymapBinding(chord, label);
233
+ if (!existsSync(keymapPath)) {
234
+ mkdirSync(dirname(keymapPath), { recursive: true });
235
+ writeFileSync(keymapPath, `${JSON.stringify([binding], null, 2)}
236
+ `);
237
+ return true;
238
+ }
239
+ const raw = readFileSync(keymapPath, "utf8");
240
+ const errors = [];
241
+ const parsed = parse(raw, errors, {
242
+ allowTrailingComma: true,
243
+ disallowComments: false
244
+ });
245
+ if (errors.length > 0 || !Array.isArray(parsed)) {
246
+ process.stderr.write(
247
+ `
248
+ Warning: could not parse ${keymapPath}; not modifying it. Add this binding manually:
249
+ ${JSON.stringify(binding, null, 2)}
250
+ `
251
+ );
252
+ return false;
253
+ }
254
+ const keymap = parsed;
255
+ if (upsertKeymapBinding(keymap, chord, label) === keymap) {
256
+ return true;
257
+ }
258
+ if (hasConflictingChord(keymap, chord, label)) {
259
+ process.stderr.write(
260
+ `
261
+ Note: "${chord}" is already bound in ${keymapPath}; wt's agent binding will take precedence.
262
+ `
263
+ );
264
+ }
265
+ const edits = modify(raw, [keymap.length], binding, {
266
+ isArrayInsertion: true,
267
+ formattingOptions: { insertSpaces: true, tabSize: 2 }
268
+ });
269
+ const updated = applyEdits(raw, edits);
270
+ writeFileSync(keymapPath, updated.endsWith("\n") ? updated : `${updated}
271
+ `);
272
+ return true;
273
+ }
274
+ var ACCESSIBILITY_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
275
+ function defaultRunner(script) {
276
+ return new Promise((resolve) => {
277
+ const child = spawn("osascript", ["-e", script], {
278
+ stdio: ["ignore", "ignore", "pipe"]
279
+ });
280
+ let stderr = "";
281
+ child.stderr?.on("data", (d) => {
282
+ stderr += d.toString();
283
+ });
284
+ child.on("error", (err) => resolve({ code: null, stderr: err.message }));
285
+ child.on("close", (code) => resolve({ code, stderr }));
286
+ });
287
+ }
288
+ function isAccessibilityError(stderr) {
289
+ return /\b1002\b/.test(stderr) || /-1719\b/.test(stderr) || /not allowed to send keystrokes/i.test(stderr) || /not allowed assistive access/i.test(stderr);
290
+ }
291
+ async function triggerChord(chord, opts = {}) {
292
+ if (process.platform !== "darwin") {
293
+ return { ok: false, reason: "unsupported" };
294
+ }
295
+ const { runner = defaultRunner, loadDelay, activateDelay } = opts;
296
+ const { code, stderr } = await runner(
297
+ buildOsascript(chord, { loadDelay, activateDelay })
298
+ );
299
+ if (code === 0) return { ok: true };
300
+ if (isAccessibilityError(stderr)) {
301
+ return { ok: false, reason: "accessibility", message: stderr.trim() };
302
+ }
303
+ return {
304
+ ok: false,
305
+ reason: "error",
306
+ message: stderr.trim() || `osascript exited with code ${code}`
307
+ };
308
+ }
309
+ function defaultOpen(url) {
310
+ const child = spawn("open", [url], { detached: true, stdio: "ignore" });
311
+ child.on("error", () => {
312
+ });
313
+ child.unref();
314
+ }
315
+ function openAccessibilitySettings(open = defaultOpen) {
316
+ if (process.platform !== "darwin") return;
317
+ open(ACCESSIBILITY_SETTINGS_URL);
318
+ }
319
+
320
+ // src/commands/agent.ts
321
+ var CLEANUP_DELAY_MS = 2e4;
322
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
323
+ async function createAgentWorktree(branch, planPrompt, options = {}) {
324
+ const prepared = await prepareWorktree(branch, options);
325
+ if (!prepared) return;
326
+ const { config, worktreePath } = prepared;
327
+ if (config.ide !== "zed") {
328
+ console.warn(
329
+ pc.yellow(
330
+ `\u26A0 Agent auto-start requires Zed (ide is "${config.ide}"). Opening without starting the agent.`
331
+ )
332
+ );
333
+ await openConfiguredIde(config, worktreePath);
334
+ return;
335
+ }
336
+ if (!config.agent_command) {
337
+ console.error(
338
+ pc.red("No agent_command configured. Set it with `wt config`.")
339
+ );
340
+ await openConfiguredIde(config, worktreePath);
341
+ return;
342
+ }
343
+ const task = buildAgentTask(
344
+ config.agent_command,
345
+ planPrompt,
346
+ AGENT_TASK_LABEL
347
+ );
348
+ const created = writeAgentTask(worktreePath, task);
349
+ const keymapOk = ensureKeymap(config.agent_trigger_chord, AGENT_TASK_LABEL);
350
+ const opened = await openConfiguredIde(config, worktreePath);
351
+ if (!opened) {
352
+ console.error(pc.red("\u2717 Could not open Zed."));
353
+ cleanupAgentTask(worktreePath, AGENT_TASK_LABEL, created);
354
+ return;
355
+ }
356
+ if (!keymapOk) {
357
+ console.warn(
358
+ pc.yellow(
359
+ `\u26A0 Could not install the Zed keybinding (see above). In Zed, press ${config.agent_trigger_chord} to start the agent manually.`
360
+ )
361
+ );
362
+ return;
363
+ }
364
+ console.log(pc.dim("Starting agent in Zed\u2026"));
365
+ let result = await triggerChord(config.agent_trigger_chord);
366
+ while (!result.ok && result.reason === "accessibility" && process.stdin.isTTY) {
367
+ console.warn(
368
+ pc.yellow(
369
+ "\u26A0 macOS Accessibility permission is required to send the keystroke that starts the agent."
370
+ )
371
+ );
372
+ openAccessibilitySettings();
373
+ const proceed = await clack.confirm({
374
+ message: "Grant Accessibility to the app running wt (e.g. Zed) in the panel that opened, then confirm to retry. (If that app was already running, you may need to quit and reopen it for the grant to take effect.)"
375
+ });
376
+ if (clack.isCancel(proceed) || !proceed) break;
377
+ result = await triggerChord(config.agent_trigger_chord, {
378
+ loadDelay: 0,
379
+ activateDelay: 0.5
380
+ });
381
+ }
382
+ if (!result.ok) {
383
+ reportTriggerFailure(result, config.agent_trigger_chord);
384
+ return;
385
+ }
386
+ console.log(pc.green("\u2713 Agent started"));
387
+ await delay(CLEANUP_DELAY_MS);
388
+ cleanupAgentTask(worktreePath, AGENT_TASK_LABEL, created);
389
+ }
390
+ function reportTriggerFailure(result, chord) {
391
+ if (result.reason === "unsupported") {
392
+ console.warn(
393
+ pc.yellow(
394
+ `\u26A0 Agent auto-start is only supported on macOS. The worktree and Zed are open; press ${chord} in Zed to start the agent.`
395
+ )
396
+ );
397
+ return;
398
+ }
399
+ if (result.reason === "accessibility") {
400
+ console.warn(
401
+ pc.yellow(
402
+ `\u26A0 Agent not started (Accessibility not granted). In Zed, press ${chord} to start it manually.`
403
+ )
404
+ );
405
+ return;
406
+ }
407
+ console.warn(
408
+ pc.yellow(
409
+ `\u26A0 Could not auto-start the agent${result.message ? `: ${result.message}` : ""}. In Zed, press ${chord} to start it manually.`
410
+ )
411
+ );
412
+ }
413
+ export {
414
+ createAgentWorktree
415
+ };
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  createStore,
4
4
  getGlobalConfig
5
- } from "./chunk-HIGP6FSR.js";
5
+ } from "./chunk-IQWENLPW.js";
6
6
 
7
7
  // src/lib/git.ts
8
8
  import { execFileSync } from "child_process";
9
- import { realpathSync } from "fs";
9
+ import { existsSync, realpathSync, rmSync } from "fs";
10
10
  import path from "path";
11
11
  function getRepoRoot(cwd = process.cwd()) {
12
12
  try {
@@ -75,11 +75,22 @@ function addWorktree(repoRoot, worktreePath, branch, baseBranch) {
75
75
  }
76
76
  }
77
77
  function removeWorktree(repoRoot, worktreePath, force = false) {
78
- execFileSync(
79
- "git",
80
- ["worktree", "remove", ...force ? ["--force"] : [], worktreePath],
81
- { cwd: repoRoot, stdio: "pipe" }
82
- );
78
+ try {
79
+ execFileSync(
80
+ "git",
81
+ ["worktree", "remove", ...force ? ["--force"] : [], worktreePath],
82
+ { cwd: repoRoot, stdio: "pipe" }
83
+ );
84
+ } catch (err) {
85
+ if (!force) throw err;
86
+ if (existsSync(worktreePath)) {
87
+ rmSync(worktreePath, { recursive: true, force: true });
88
+ }
89
+ execFileSync("git", ["worktree", "prune"], {
90
+ cwd: repoRoot,
91
+ stdio: "pipe"
92
+ });
93
+ }
83
94
  }
84
95
  function listWorktreeDirtyFiles(worktreePath) {
85
96
  try {
@@ -113,6 +124,13 @@ function branchExists(repoRoot, branch) {
113
124
  return false;
114
125
  }
115
126
  }
127
+ function fetchRemote(repoRoot, remote = "origin") {
128
+ execFileSync("git", ["fetch", remote], {
129
+ cwd: repoRoot,
130
+ stdio: "pipe",
131
+ timeout: 3e4
132
+ });
133
+ }
116
134
  function resolveWorktreePath(repoRoot, worktreePath, branch) {
117
135
  const repoName = path.basename(repoRoot);
118
136
  const safeBranch = branch.replace(/\//g, "-");
@@ -480,6 +498,7 @@ export {
480
498
  removeWorktree,
481
499
  listWorktreeDirtyFiles,
482
500
  branchExists,
501
+ fetchRemote,
483
502
  resolveWorktreePath,
484
503
  openIde,
485
504
  registerRepo,
@@ -8,6 +8,8 @@ var DEFAULT_CONFIG = {
8
8
  setup_commands: [],
9
9
  ide: "zed",
10
10
  ide_open_args: ["-n"],
11
+ agent_command: "claude --permission-mode plan",
12
+ agent_trigger_chord: "ctrl-shift-cmd-c",
11
13
  repos: [],
12
14
  repo_overrides: {}
13
15
  };
@@ -2,6 +2,7 @@
2
2
  import {
3
3
  addWorktree,
4
4
  branchExists,
5
+ fetchRemote,
5
6
  getRegisteredRepos,
6
7
  getRepoRoot,
7
8
  openIde,
@@ -9,11 +10,11 @@ import {
9
10
  resolveWorktreePath,
10
11
  runBranchInput,
11
12
  runRepoPicker
12
- } from "./chunk-25JVZOCI.js";
13
+ } from "./chunk-FSARWOCW.js";
13
14
  import {
14
15
  createStore,
15
16
  getEffectiveConfig
16
- } from "./chunk-HIGP6FSR.js";
17
+ } from "./chunk-IQWENLPW.js";
17
18
 
18
19
  // src/commands/create.ts
19
20
  import { existsSync } from "fs";
@@ -48,7 +49,7 @@ function runCommand(command, cwd) {
48
49
  }
49
50
 
50
51
  // src/commands/create.ts
51
- async function createWorktree(branch, options = {}) {
52
+ async function prepareWorktree(branch, options = {}) {
52
53
  const {
53
54
  cwd = process.cwd(),
54
55
  store = createStore(),
@@ -66,7 +67,7 @@ async function createWorktree(branch, options = {}) {
66
67
  "No repos registered. cd into a repo and run wt create to get started."
67
68
  )
68
69
  );
69
- return;
70
+ return null;
70
71
  }
71
72
  if (!process.stdin.isTTY) {
72
73
  console.error(
@@ -77,25 +78,38 @@ async function createWorktree(branch, options = {}) {
77
78
  process.exit(1);
78
79
  }
79
80
  const picked = await repoPicker(repos);
80
- if (!picked) return;
81
+ if (!picked) return null;
81
82
  repoRoot = picked;
82
83
  if (!branch) {
83
84
  const entered = await branchInput(repoRoot);
84
- if (!entered) return;
85
+ if (!entered) return null;
85
86
  branch = entered;
86
87
  }
87
88
  }
88
- if (!repoRoot) return;
89
+ if (!repoRoot) return null;
89
90
  if (!branch) {
90
91
  const input = await clack.text({
91
92
  message: "Branch name:",
92
93
  validate: (v) => !v || v.length === 0 ? "Required" : void 0
93
94
  });
94
- if (clack.isCancel(input)) return;
95
+ if (clack.isCancel(input)) return null;
95
96
  branch = input;
96
97
  }
97
98
  registerRepo(repoRoot, store);
98
99
  const config = getEffectiveConfig(repoRoot, store);
100
+ const parts = config.base_branch.split("/", 2);
101
+ if (parts.length === 2) {
102
+ const remote = parts[0];
103
+ try {
104
+ fetchRemote(repoRoot, remote);
105
+ } catch (err) {
106
+ console.warn(
107
+ pc.yellow(
108
+ `\u26A0 Could not fetch from ${remote} \u2014 using local state${err instanceof Error ? ` (${err.message})` : ""}`
109
+ )
110
+ );
111
+ }
112
+ }
99
113
  const worktreePath = resolveWorktreePath(
100
114
  repoRoot,
101
115
  config.worktree_path,
@@ -124,17 +138,24 @@ async function createWorktree(branch, options = {}) {
124
138
  process.exit(1);
125
139
  }
126
140
  }
127
- if (config.ide) {
128
- const opened = await openIde(
129
- config.ide,
130
- config.ide_open_args,
131
- worktreePath
132
- );
133
- if (opened) {
134
- console.log(pc.green(`\u2713 Opened ${config.ide}`));
135
- }
141
+ return { repoRoot, worktreePath, config };
142
+ }
143
+ async function openConfiguredIde(config, worktreePath) {
144
+ if (!config.ide) return false;
145
+ const opened = await openIde(config.ide, config.ide_open_args, worktreePath);
146
+ if (opened) {
147
+ console.log(pc.green(`\u2713 Opened ${config.ide}`));
136
148
  }
149
+ return opened;
137
150
  }
151
+ async function createWorktree(branch, options = {}) {
152
+ const prepared = await prepareWorktree(branch, options);
153
+ if (!prepared) return;
154
+ await openConfiguredIde(prepared.config, prepared.worktreePath);
155
+ }
156
+
138
157
  export {
158
+ prepareWorktree,
159
+ openConfiguredIde,
139
160
  createWorktree
140
161
  };
package/dist/cli.js CHANGED
@@ -3,16 +3,29 @@
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").action(async () => {
7
- const { runList } = await import("./list-Q7CFJ66L.js");
6
+ program.name("wt").description("Git worktree manager").version("0.1.0-pr5.g3ca944a").action(async () => {
7
+ const { runList } = await import("./list-KG6HKRMK.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-42OYRFQF.js");
11
+ const { createWorktree } = await import("./create-66LRJRWI.js");
12
12
  await createWorktree(branch);
13
13
  });
14
- program.command("config").description("Open the config file in $EDITOR").action(async () => {
15
- const { openConfig } = await import("./config-FRLXDMRY.js");
16
- openConfig();
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-SKQRHH3U.js");
16
+ await createAgentWorktree(branch, planPrompt);
17
+ });
18
+ program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
19
+ if (options.path) {
20
+ const { printConfigPath } = await import("./config-OSNYQCZL.js");
21
+ printConfigPath();
22
+ } else {
23
+ const { openConfig } = await import("./config-OSNYQCZL.js");
24
+ openConfig();
25
+ }
26
+ });
27
+ program.command("skill").description("Print the wt skill file to stdout").action(async () => {
28
+ const { printSkill } = await import("./skill-CRAP2ALY.js");
29
+ printSkill();
17
30
  });
18
31
  await program.parseAsync(process.argv);
@@ -1,13 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createStore
4
- } from "./chunk-HIGP6FSR.js";
4
+ } from "./chunk-IQWENLPW.js";
5
5
 
6
6
  // src/commands/config.ts
7
7
  import { spawn } from "child_process";
8
8
  function getConfigPath(store = createStore()) {
9
9
  return store.path;
10
10
  }
11
+ function printConfigPath(store = createStore()) {
12
+ console.log(store.path);
13
+ }
11
14
  function openConfig(store = createStore()) {
12
15
  const configPath = store.path;
13
16
  console.log(`Config: ${configPath}`);
@@ -21,5 +24,6 @@ function openConfig(store = createStore()) {
21
24
  }
22
25
  export {
23
26
  getConfigPath,
24
- openConfig
27
+ openConfig,
28
+ printConfigPath
25
29
  };
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createWorktree,
4
+ openConfiguredIde,
5
+ prepareWorktree
6
+ } from "./chunk-YNGWUXCX.js";
7
+ import "./chunk-FSARWOCW.js";
8
+ import "./chunk-IQWENLPW.js";
9
+ export {
10
+ createWorktree,
11
+ openConfiguredIde,
12
+ prepareWorktree
13
+ };
@@ -8,11 +8,11 @@ import {
8
8
  registerRepo,
9
9
  removeWorktree,
10
10
  runInteractiveList
11
- } from "./chunk-25JVZOCI.js";
11
+ } from "./chunk-FSARWOCW.js";
12
12
  import {
13
13
  createStore,
14
14
  getEffectiveConfig
15
- } from "./chunk-HIGP6FSR.js";
15
+ } from "./chunk-IQWENLPW.js";
16
16
 
17
17
  // src/commands/list.ts
18
18
  import * as clack from "@clack/prompts";
@@ -93,7 +93,7 @@ ${dirty.map((f) => ` ${f}`).join("\n")}`
93
93
  },
94
94
  onCreate: async () => {
95
95
  if (repoRoot) {
96
- const { createWorktree } = await import("./create-42OYRFQF.js");
96
+ const { createWorktree } = await import("./create-66LRJRWI.js");
97
97
  await createWorktree(void 0, { cwd: repoRoot, store });
98
98
  }
99
99
  }
@@ -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\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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cestoliv/wt",
3
- "version": "0.1.0-pr1.f956696",
3
+ "version": "0.1.0-pr5.g3ca944a",
4
4
  "description": "Fast, interactive TUI to browse, create, open, and delete git worktrees across repos.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,8 @@
14
14
  "wt": "./dist/cli.js"
15
15
  },
16
16
  "files": [
17
- "dist"
17
+ "dist",
18
+ "SKILL.md"
18
19
  ],
19
20
  "publishConfig": {
20
21
  "access": "public"
@@ -34,6 +35,7 @@
34
35
  "commander": "^14.0.3",
35
36
  "conf": "^15.1.0",
36
37
  "fuse.js": "^7.1.0",
38
+ "jsonc-parser": "^3.3.1",
37
39
  "picocolors": "^1.1.1"
38
40
  },
39
41
  "devDependencies": {