@cestoliv/wt 0.3.0 → 0.4.0-pr17.g3b7d0a6
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 +31 -18
- package/SKILL.md +34 -15
- package/dist/{agent-3N6CSK7A.js → agent-CIGIZGIS.js} +128 -14
- package/dist/{chunk-B6EPGPXW.js → chunk-BSMGKL27.js} +4 -32
- package/dist/chunk-FNAMNRUH.js +115 -0
- package/dist/{chunk-DKFHAASV.js → chunk-XM4EABZV.js} +82 -17
- package/dist/cli.js +16 -10
- package/dist/{config-OSNYQCZL.js → config-DBM7HJE4.js} +7 -10
- package/dist/{create-5BZUA7QH.js → create-WRMXQIGN.js} +3 -3
- package/dist/{list-FOLV77ET.js → list-WC5GM3PI.js} +18 -3
- package/dist/skill-FEUVQMK6.js +9 -0
- package/package.json +1 -1
- package/dist/chunk-IQWENLPW.js +0 -40
- package/dist/skill-RR6MNLWH.js +0 -9
package/README.md
CHANGED
|
@@ -34,30 +34,42 @@ infer from this project. Write the result to the config file (find its path with
|
|
|
34
34
|
## Quick start
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
wt
|
|
38
|
-
wt create my-feat
|
|
39
|
-
wt agent my-feat "Plan the feature"
|
|
40
|
-
wt
|
|
41
|
-
wt
|
|
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 agent fix-bug "Fix bug" --mode auto # Use auto mode instead of plan
|
|
41
|
+
wt config # Edit config in $EDITOR
|
|
42
|
+
wt skill # Print the skill file (for AI agents)
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
## `wt agent <branch> <plan_prompt
|
|
45
|
+
## `wt agent <branch> <plan_prompt> [--mode <mode>]` — the standout
|
|
45
46
|
|
|
46
47
|
```bash
|
|
47
48
|
wt agent feat/login "Read the codebase, then propose a plan for login."
|
|
49
|
+
wt agent fix-bug "Fix the auth bug" --mode auto
|
|
50
|
+
wt agent refactor "Refactor API layer" --mode default
|
|
48
51
|
```
|
|
49
52
|
|
|
50
53
|
Creates a worktree exactly like `wt create`, then auto-starts your agent
|
|
51
54
|
(default `claude --permission-mode plan`) in Zed's integrated terminal —
|
|
52
55
|
pre-filled with your prompt and left interactive for you to take over.
|
|
53
56
|
|
|
57
|
+
**Available modes** (`--mode`, defaults to `plan`):
|
|
58
|
+
|
|
59
|
+
- `default` — Standard interactive mode with approval for each action
|
|
60
|
+
- `acceptEdits` — Allow file changes but keep command execution controlled
|
|
61
|
+
- `plan` — Architecture-first mode with no surprise mutations (default)
|
|
62
|
+
- `auto` — Claude's safety model makes decisions instead of prompting
|
|
63
|
+
- `dontAsk` — Minimal interruptions in trusted environments
|
|
64
|
+
- `bypassPermissions` — Skip all permission checks (dangerous, CI/sandbox only)
|
|
65
|
+
|
|
54
66
|
Under the hood it writes a temporary `.zed/tasks.json`, installs a global Zed
|
|
55
67
|
keymap chord, opens Zed and fires the chord via `osascript`, then removes the
|
|
56
68
|
temp task so the repo stays clean.
|
|
57
69
|
|
|
58
70
|
**Requires** macOS, Zed, and Accessibility permission for the app running `wt`.
|
|
59
|
-
Not granted yet? `wt agent` opens
|
|
60
|
-
|
|
71
|
+
Not granted yet? `wt agent` opens _System Settings → Privacy & Security →
|
|
72
|
+
Accessibility_, waits while you grant it (you may need to quit and reopen the
|
|
61
73
|
app), then retries automatically. On other platforms — or when `ide` isn't
|
|
62
74
|
`zed` — the worktree is still created and opened, just without the agent.
|
|
63
75
|
|
|
@@ -104,16 +116,17 @@ erroring (in a non-interactive shell it exits non-zero).
|
|
|
104
116
|
Edit with `wt config` (`wt config --path` prints the file location —
|
|
105
117
|
`~/Library/Preferences/wt-nodejs/config.json` on macOS).
|
|
106
118
|
|
|
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
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
119
|
+
| Key | Default | Description |
|
|
120
|
+
| --------------------- | --------------------------------- | ----------------------------------------------------------------------------------- |
|
|
121
|
+
| `ide` | `"zed"` | Editor to open worktrees with |
|
|
122
|
+
| `ide_open_args` | `["-n"]` | Extra args passed to the IDE command |
|
|
123
|
+
| `base_branch` | `"origin/main"` | Branch new worktrees are created from |
|
|
124
|
+
| `worktree_path` | `"../"` | Where worktrees are placed (relative to repo) |
|
|
125
|
+
| `setup_commands` | `[]` | Commands to run in new worktrees |
|
|
126
|
+
| `teardown_commands` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`) |
|
|
127
|
+
| `agent_command` | `"claude --permission-mode plan"` | Base command; `--permission-mode` replaced by `--mode` option, then prompt appended |
|
|
128
|
+
| `agent_trigger_chord` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs and presses |
|
|
129
|
+
| `repo_overrides` | `{}` | Per-repo overrides for any key above |
|
|
117
130
|
|
|
118
131
|
Override any key per repo:
|
|
119
132
|
|
package/SKILL.md
CHANGED
|
@@ -34,7 +34,7 @@ If the worktree path already exists, `wt create` doesn't error — it prompts yo
|
|
|
34
34
|
to **open it in the IDE** or **quit**. (In a non-interactive shell it errors
|
|
35
35
|
with a non-zero exit instead of prompting.)
|
|
36
36
|
|
|
37
|
-
### `wt agent <branch> <plan_prompt
|
|
37
|
+
### `wt agent <branch> <plan_prompt> [--mode <mode>]`
|
|
38
38
|
|
|
39
39
|
Create a worktree (same as `wt create`) **and** auto-start an AI agent in Zed's
|
|
40
40
|
integrated terminal, pre-filled with `<plan_prompt>` and left interactive for
|
|
@@ -42,20 +42,38 @@ you to take over.
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
wt agent feature/login 'Read the codebase, then propose a plan for login.'
|
|
45
|
+
wt agent feature/fix 'Fix the bug in payment processing' --mode auto
|
|
46
|
+
wt agent refactor/api 'Refactor the API layer' --mode default
|
|
45
47
|
```
|
|
46
48
|
|
|
49
|
+
The `--mode` flag sets Claude Code's permission mode (defaults to `plan`):
|
|
50
|
+
|
|
51
|
+
- `default` — Standard interactive mode with approval for each action
|
|
52
|
+
- `acceptEdits` — Allow file changes but keep command execution controlled
|
|
53
|
+
- `plan` — Architecture-first mode with no surprise mutations (default)
|
|
54
|
+
- `auto` — Claude's safety model makes decisions instead of prompting
|
|
55
|
+
- `dontAsk` — Minimal interruptions in trusted environments
|
|
56
|
+
- `bypassPermissions` — Skip all permission checks (dangerous, CI/sandbox only)
|
|
57
|
+
|
|
47
58
|
It writes a temporary `.zed/tasks.json` running
|
|
48
|
-
`<agent_command> '<plan_prompt>'`, ensures a global Zed keymap chord
|
|
59
|
+
`<agent_command> --permission-mode <mode> '<plan_prompt>'`, ensures a global Zed keymap chord
|
|
49
60
|
(`agent_trigger_chord`) spawns that task, opens Zed, presses the chord via
|
|
50
61
|
`osascript`, then removes the temporary task so the repo is left clean.
|
|
51
62
|
|
|
52
63
|
**macOS + Zed only.** Requires Accessibility permission for the app that runs
|
|
53
64
|
`wt` (Zed itself, when run from its integrated terminal). If it isn't granted,
|
|
54
|
-
`wt agent` opens the
|
|
65
|
+
`wt agent` opens the _Privacy & Security → Accessibility_ settings pane and waits
|
|
55
66
|
for you to grant it and confirm, then retries automatically. On other platforms
|
|
56
67
|
(or when `ide` is not `zed`) the worktree is still created and opened, but the
|
|
57
68
|
agent is not auto-started.
|
|
58
69
|
|
|
70
|
+
Over SSH it still works, provided the same user has an active graphical login on
|
|
71
|
+
the Mac: the keystroke is run inside the GUI session via Launch Services
|
|
72
|
+
(`open -a Terminal` briefly flashes a Terminal window). Grant Accessibility to
|
|
73
|
+
Terminal (not Zed) the first time. With no one logged in graphically there is
|
|
74
|
+
nothing to drive, so it falls back to the manual "press the chord in Zed"
|
|
75
|
+
message.
|
|
76
|
+
|
|
59
77
|
If the worktree path already exists, `wt agent` prompts you to **open it in the
|
|
60
78
|
IDE**, **open it and start the agent**, or **quit** — instead of erroring. (In a
|
|
61
79
|
non-interactive shell it errors with a non-zero exit instead of prompting.)
|
|
@@ -79,21 +97,22 @@ Config is stored as JSON. Get the path with `wt config --path`.
|
|
|
79
97
|
|
|
80
98
|
### Schema
|
|
81
99
|
|
|
82
|
-
| Key
|
|
83
|
-
|
|
|
84
|
-
| `worktree_path`
|
|
85
|
-
| `base_branch`
|
|
86
|
-
| `setup_commands`
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
100
|
+
| Key | Type | Default | Description |
|
|
101
|
+
| --------------------- | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
102
|
+
| `worktree_path` | `string` | `"../"` | Where to place new worktrees, relative to the repo root |
|
|
103
|
+
| `base_branch` | `string` | `"origin/main"` | Branch to base new worktrees on |
|
|
104
|
+
| `setup_commands` | `string[]` | `[]` | Commands to run in a new worktree after creation (e.g. `["npm install"]`) |
|
|
105
|
+
| `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 |
|
|
106
|
+
| `ide` | `string` | `"zed"` | IDE command to open worktrees with |
|
|
107
|
+
| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |
|
|
108
|
+
| `agent_command` | `string` | `"claude --permission-mode plan"` | Base command `wt agent` runs in Zed; any `--permission-mode` flag is replaced by the `--mode` option (defaults to `plan`), then `<plan_prompt>` is appended single-quoted |
|
|
109
|
+
| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |
|
|
110
|
+
| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |
|
|
111
|
+
| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |
|
|
93
112
|
|
|
94
113
|
### Per-repo overrides
|
|
95
114
|
|
|
96
|
-
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:
|
|
115
|
+
Override any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:
|
|
97
116
|
|
|
98
117
|
```json
|
|
99
118
|
{
|
|
@@ -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-BSMGKL27.js";
|
|
7
|
+
import "./chunk-XM4EABZV.js";
|
|
8
|
+
import "./chunk-FNAMNRUH.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/agent.ts
|
|
11
11
|
import * as clack from "@clack/prompts";
|
|
@@ -14,22 +14,29 @@ import pc from "picocolors";
|
|
|
14
14
|
// src/lib/zed.ts
|
|
15
15
|
import { spawn } from "child_process";
|
|
16
16
|
import {
|
|
17
|
+
chmodSync,
|
|
17
18
|
existsSync,
|
|
18
19
|
mkdirSync,
|
|
20
|
+
mkdtempSync,
|
|
19
21
|
readdirSync,
|
|
20
22
|
readFileSync,
|
|
21
23
|
rmSync,
|
|
22
24
|
writeFileSync
|
|
23
25
|
} from "fs";
|
|
24
|
-
import { homedir } from "os";
|
|
26
|
+
import { homedir, tmpdir } from "os";
|
|
25
27
|
import { dirname, join } from "path";
|
|
26
28
|
import { applyEdits, modify, parse } from "jsonc-parser";
|
|
27
29
|
var AGENT_TASK_LABEL = "wt: agent";
|
|
28
|
-
function buildAgentTask(agentCommand, prompt, label) {
|
|
30
|
+
function buildAgentTask(agentCommand, prompt, label, mode) {
|
|
31
|
+
let finalCommand = agentCommand;
|
|
32
|
+
if (mode) {
|
|
33
|
+
const baseCommand = agentCommand.replace(/--permission-mode\s+\S+/g, "").replace(/\s+/g, " ").trim();
|
|
34
|
+
finalCommand = `${baseCommand} --permission-mode ${mode}`.trim();
|
|
35
|
+
}
|
|
29
36
|
const escaped = prompt.replace(/'/g, "'\\''");
|
|
30
37
|
return {
|
|
31
38
|
label,
|
|
32
|
-
command: `${
|
|
39
|
+
command: `${finalCommand} '${escaped}'`,
|
|
33
40
|
cwd: "$ZED_WORKTREE_ROOT",
|
|
34
41
|
use_new_terminal: true,
|
|
35
42
|
allow_concurrent_runs: false,
|
|
@@ -273,19 +280,109 @@ Note: "${chord}" is already bound in ${keymapPath}; wt's agent binding will take
|
|
|
273
280
|
return true;
|
|
274
281
|
}
|
|
275
282
|
var ACCESSIBILITY_SETTINGS_URL = "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility";
|
|
276
|
-
function
|
|
283
|
+
function isHeadlessSession(env = process.env) {
|
|
284
|
+
return Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
|
|
285
|
+
}
|
|
286
|
+
function buildGuiHelperScript(scriptPath, resultPath) {
|
|
287
|
+
const q = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
288
|
+
return [
|
|
289
|
+
"#!/bin/sh",
|
|
290
|
+
`out=$(osascript ${q(scriptPath)} 2>&1)`,
|
|
291
|
+
"code=$?",
|
|
292
|
+
`printf '%s\\n%s' "$code" "$out" > ${q(`${resultPath}.tmp`)} && mv ${q(`${resultPath}.tmp`)} ${q(resultPath)}`,
|
|
293
|
+
`osascript -e 'tell application "Terminal" to close front window' >/dev/null 2>&1 &`,
|
|
294
|
+
""
|
|
295
|
+
].join("\n");
|
|
296
|
+
}
|
|
297
|
+
function parseGuiResult(content) {
|
|
298
|
+
const newline = content.indexOf("\n");
|
|
299
|
+
const codeStr = (newline === -1 ? content : content.slice(0, newline)).trim();
|
|
300
|
+
const stderr = newline === -1 ? "" : content.slice(newline + 1);
|
|
301
|
+
const code = Number.parseInt(codeStr, 10);
|
|
302
|
+
if (Number.isNaN(code)) {
|
|
303
|
+
return {
|
|
304
|
+
code: null,
|
|
305
|
+
stderr: stderr || `unexpected result format (first line: ${JSON.stringify(codeStr)})`
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
return { code, stderr };
|
|
309
|
+
}
|
|
310
|
+
var OSASCRIPT_TIMEOUT_MS = 3e4;
|
|
311
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
312
|
+
function spawnOpen(helperPath) {
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
const child = spawn("open", ["-a", "Terminal", helperPath], {
|
|
315
|
+
stdio: "ignore"
|
|
316
|
+
});
|
|
317
|
+
child.on("error", reject);
|
|
318
|
+
child.on("close", () => resolve());
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function pollFile(path, timeoutMs) {
|
|
322
|
+
const deadline = Date.now() + timeoutMs;
|
|
323
|
+
while (Date.now() < deadline) {
|
|
324
|
+
if (existsSync(path)) return readFileSync(path, "utf8");
|
|
325
|
+
await sleep(200);
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
async function runViaGuiHelper(script) {
|
|
330
|
+
const dir = mkdtempSync(join(tmpdir(), "wt-agent-"));
|
|
331
|
+
const scriptPath = join(dir, "chord.applescript");
|
|
332
|
+
const helperPath = join(dir, "run.command");
|
|
333
|
+
const resultPath = join(dir, "result");
|
|
334
|
+
try {
|
|
335
|
+
writeFileSync(scriptPath, script);
|
|
336
|
+
writeFileSync(helperPath, buildGuiHelperScript(scriptPath, resultPath));
|
|
337
|
+
chmodSync(helperPath, 493);
|
|
338
|
+
await spawnOpen(helperPath);
|
|
339
|
+
const content = await pollFile(resultPath, OSASCRIPT_TIMEOUT_MS);
|
|
340
|
+
if (content === null) {
|
|
341
|
+
return {
|
|
342
|
+
code: null,
|
|
343
|
+
stderr: "Timed out waiting for the keystroke \u2014 is a user logged into the Mac's graphical session?"
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return parseGuiResult(content);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
return {
|
|
349
|
+
code: null,
|
|
350
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
351
|
+
};
|
|
352
|
+
} finally {
|
|
353
|
+
rmSync(dir, { recursive: true, force: true });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function runOsascriptDirect(script) {
|
|
277
357
|
return new Promise((resolve) => {
|
|
278
358
|
const child = spawn("osascript", ["-e", script], {
|
|
279
359
|
stdio: ["ignore", "ignore", "pipe"]
|
|
280
360
|
});
|
|
281
361
|
let stderr = "";
|
|
362
|
+
let settled = false;
|
|
363
|
+
const finish = (result) => {
|
|
364
|
+
if (settled) return;
|
|
365
|
+
settled = true;
|
|
366
|
+
clearTimeout(timer);
|
|
367
|
+
resolve(result);
|
|
368
|
+
};
|
|
369
|
+
const timer = setTimeout(() => {
|
|
370
|
+
child.kill();
|
|
371
|
+
finish({
|
|
372
|
+
code: null,
|
|
373
|
+
stderr: "osascript timed out \u2014 is a user logged into the Mac's graphical session?"
|
|
374
|
+
});
|
|
375
|
+
}, OSASCRIPT_TIMEOUT_MS);
|
|
282
376
|
child.stderr?.on("data", (d) => {
|
|
283
377
|
stderr += d.toString();
|
|
284
378
|
});
|
|
285
|
-
child.on("error", (err) =>
|
|
286
|
-
child.on("close", (code) =>
|
|
379
|
+
child.on("error", (err) => finish({ code: null, stderr: err.message }));
|
|
380
|
+
child.on("close", (code) => finish({ code, stderr }));
|
|
287
381
|
});
|
|
288
382
|
}
|
|
383
|
+
function defaultRunner(script) {
|
|
384
|
+
return isHeadlessSession() ? runViaGuiHelper(script) : runOsascriptDirect(script);
|
|
385
|
+
}
|
|
289
386
|
function isAccessibilityError(stderr) {
|
|
290
387
|
return /\b1002\b/.test(stderr) || /-1719\b/.test(stderr) || /not allowed to send keystrokes/i.test(stderr) || /not allowed assistive access/i.test(stderr);
|
|
291
388
|
}
|
|
@@ -319,9 +416,24 @@ function openAccessibilitySettings(open = defaultOpen) {
|
|
|
319
416
|
}
|
|
320
417
|
|
|
321
418
|
// src/commands/agent.ts
|
|
419
|
+
var VALID_MODES = [
|
|
420
|
+
"default",
|
|
421
|
+
"acceptEdits",
|
|
422
|
+
"plan",
|
|
423
|
+
"auto",
|
|
424
|
+
"dontAsk",
|
|
425
|
+
"bypassPermissions"
|
|
426
|
+
];
|
|
322
427
|
var CLEANUP_DELAY_MS = 2e4;
|
|
323
428
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
324
429
|
async function createAgentWorktree(branch, planPrompt, options = {}) {
|
|
430
|
+
const mode = options.mode ?? "plan";
|
|
431
|
+
if (!VALID_MODES.includes(mode)) {
|
|
432
|
+
console.error(
|
|
433
|
+
pc.red(`Invalid mode "${mode}". Valid modes: ${VALID_MODES.join(", ")}`)
|
|
434
|
+
);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
325
437
|
const prepared = await prepareWorktree(branch, options);
|
|
326
438
|
if (!prepared) return;
|
|
327
439
|
const { status, config, worktreePath } = prepared;
|
|
@@ -334,9 +446,9 @@ async function createAgentWorktree(branch, planPrompt, options = {}) {
|
|
|
334
446
|
return;
|
|
335
447
|
}
|
|
336
448
|
}
|
|
337
|
-
await startAgentInWorktree(config, worktreePath, planPrompt);
|
|
449
|
+
await startAgentInWorktree(config, worktreePath, planPrompt, mode);
|
|
338
450
|
}
|
|
339
|
-
async function startAgentInWorktree(config, worktreePath, planPrompt) {
|
|
451
|
+
async function startAgentInWorktree(config, worktreePath, planPrompt, mode) {
|
|
340
452
|
if (config.ide !== "zed") {
|
|
341
453
|
console.warn(
|
|
342
454
|
pc.yellow(
|
|
@@ -356,7 +468,8 @@ async function startAgentInWorktree(config, worktreePath, planPrompt) {
|
|
|
356
468
|
const task = buildAgentTask(
|
|
357
469
|
config.agent_command,
|
|
358
470
|
planPrompt,
|
|
359
|
-
AGENT_TASK_LABEL
|
|
471
|
+
AGENT_TASK_LABEL,
|
|
472
|
+
mode
|
|
360
473
|
);
|
|
361
474
|
const created = writeAgentTask(worktreePath, task);
|
|
362
475
|
const keymapOk = ensureKeymap(config.agent_trigger_chord, AGENT_TASK_LABEL);
|
|
@@ -383,8 +496,9 @@ async function startAgentInWorktree(config, worktreePath, planPrompt) {
|
|
|
383
496
|
)
|
|
384
497
|
);
|
|
385
498
|
openAccessibilitySettings();
|
|
499
|
+
const grantee = isHeadlessSession() ? "Terminal" : "the app running wt (e.g. Zed)";
|
|
386
500
|
const proceed = await clack.confirm({
|
|
387
|
-
message:
|
|
501
|
+
message: `Grant Accessibility to ${grantee} 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.)`
|
|
388
502
|
});
|
|
389
503
|
if (clack.isCancel(proceed) || !proceed) break;
|
|
390
504
|
result = await triggerChord(config.agent_trigger_chord, {
|
|
@@ -419,7 +533,7 @@ function reportTriggerFailure(result, chord) {
|
|
|
419
533
|
}
|
|
420
534
|
console.warn(
|
|
421
535
|
pc.yellow(
|
|
422
|
-
`\u26A0 Could not auto-start the agent${result.message ? `: ${result.message}` : ""}. In Zed, press ${chord} to start it manually
|
|
536
|
+
`\u26A0 Could not auto-start the agent${result.message ? `: ${result.message}` : ""}. In Zed, press ${chord} to start it manually. (Over SSH, this needs the same user logged into the Mac's graphical session.)`
|
|
423
537
|
)
|
|
424
538
|
);
|
|
425
539
|
}
|
|
@@ -10,47 +10,19 @@ import {
|
|
|
10
10
|
registerRepo,
|
|
11
11
|
resolveWorktreePath,
|
|
12
12
|
runBranchInput,
|
|
13
|
+
runCommands,
|
|
13
14
|
runRepoPicker,
|
|
14
15
|
setUpstreamTracking
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XM4EABZV.js";
|
|
16
17
|
import {
|
|
17
18
|
createStore,
|
|
18
19
|
getEffectiveConfig
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-FNAMNRUH.js";
|
|
20
21
|
|
|
21
22
|
// src/commands/create.ts
|
|
22
23
|
import { existsSync } from "fs";
|
|
23
24
|
import * as clack from "@clack/prompts";
|
|
24
25
|
import pc from "picocolors";
|
|
25
|
-
|
|
26
|
-
// src/lib/setup.ts
|
|
27
|
-
import { spawn } from "child_process";
|
|
28
|
-
async function runSetupCommands(commands, cwd) {
|
|
29
|
-
for (const command of commands) {
|
|
30
|
-
const result = await runCommand(command, cwd);
|
|
31
|
-
if (!result.success) {
|
|
32
|
-
return {
|
|
33
|
-
success: false,
|
|
34
|
-
failedCommand: command,
|
|
35
|
-
exitCode: result.exitCode
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return { success: true };
|
|
40
|
-
}
|
|
41
|
-
function runCommand(command, cwd) {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const child = spawn(command, { cwd, stdio: "inherit", shell: true });
|
|
44
|
-
child.on("error", () => {
|
|
45
|
-
resolve({ success: false, exitCode: void 0 });
|
|
46
|
-
});
|
|
47
|
-
child.on("close", (code) => {
|
|
48
|
-
resolve({ success: code === 0, exitCode: code ?? void 0 });
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/commands/create.ts
|
|
54
26
|
async function prepareWorktree(branch, options = {}) {
|
|
55
27
|
const {
|
|
56
28
|
cwd = process.cwd(),
|
|
@@ -141,7 +113,7 @@ async function prepareWorktree(branch, options = {}) {
|
|
|
141
113
|
console.log(pc.green(`\u2713 Created worktree at ${worktreePath}`));
|
|
142
114
|
if (config.setup_commands.length > 0) {
|
|
143
115
|
console.log(pc.dim("Running setup commands..."));
|
|
144
|
-
const result = await
|
|
116
|
+
const result = await runCommands(config.setup_commands, worktreePath);
|
|
145
117
|
if (!result.success) {
|
|
146
118
|
console.error(
|
|
147
119
|
pc.red(
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/config.ts
|
|
4
|
+
import path2 from "path";
|
|
5
|
+
import Conf from "conf";
|
|
6
|
+
|
|
7
|
+
// node_modules/env-paths/index.js
|
|
8
|
+
import path from "path";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import process2 from "process";
|
|
11
|
+
var homedir = os.homedir();
|
|
12
|
+
var tmpdir = os.tmpdir();
|
|
13
|
+
var { env } = process2;
|
|
14
|
+
var macos = (name) => {
|
|
15
|
+
const library = path.join(homedir, "Library");
|
|
16
|
+
return {
|
|
17
|
+
data: path.join(library, "Application Support", name),
|
|
18
|
+
config: path.join(library, "Preferences", name),
|
|
19
|
+
cache: path.join(library, "Caches", name),
|
|
20
|
+
log: path.join(library, "Logs", name),
|
|
21
|
+
temp: path.join(tmpdir, name)
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
var windows = (name) => {
|
|
25
|
+
const appData = env.APPDATA || path.join(homedir, "AppData", "Roaming");
|
|
26
|
+
const localAppData = env.LOCALAPPDATA || path.join(homedir, "AppData", "Local");
|
|
27
|
+
return {
|
|
28
|
+
// Data/config/cache/log are invented by me as Windows isn't opinionated about this
|
|
29
|
+
data: path.join(localAppData, name, "Data"),
|
|
30
|
+
config: path.join(appData, name, "Config"),
|
|
31
|
+
cache: path.join(localAppData, name, "Cache"),
|
|
32
|
+
log: path.join(localAppData, name, "Log"),
|
|
33
|
+
temp: path.join(tmpdir, name)
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
var linux = (name) => {
|
|
37
|
+
const username = path.basename(homedir);
|
|
38
|
+
return {
|
|
39
|
+
data: path.join(env.XDG_DATA_HOME || path.join(homedir, ".local", "share"), name),
|
|
40
|
+
config: path.join(env.XDG_CONFIG_HOME || path.join(homedir, ".config"), name),
|
|
41
|
+
cache: path.join(env.XDG_CACHE_HOME || path.join(homedir, ".cache"), name),
|
|
42
|
+
// https://wiki.debian.org/XDGBaseDirectorySpecification#state
|
|
43
|
+
log: path.join(env.XDG_STATE_HOME || path.join(homedir, ".local", "state"), name),
|
|
44
|
+
temp: path.join(tmpdir, username, name)
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
function envPaths(name, { suffix = "nodejs" } = {}) {
|
|
48
|
+
if (typeof name !== "string") {
|
|
49
|
+
throw new TypeError(`Expected a string, got ${typeof name}`);
|
|
50
|
+
}
|
|
51
|
+
if (suffix) {
|
|
52
|
+
name += `-${suffix}`;
|
|
53
|
+
}
|
|
54
|
+
if (process2.platform === "darwin") {
|
|
55
|
+
return macos(name);
|
|
56
|
+
}
|
|
57
|
+
if (process2.platform === "win32") {
|
|
58
|
+
return windows(name);
|
|
59
|
+
}
|
|
60
|
+
return linux(name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/lib/config.ts
|
|
64
|
+
var DEFAULT_CONFIG = {
|
|
65
|
+
worktree_path: "../",
|
|
66
|
+
base_branch: "origin/main",
|
|
67
|
+
setup_commands: [],
|
|
68
|
+
teardown_commands: [],
|
|
69
|
+
ide: "zed",
|
|
70
|
+
ide_open_args: ["-n"],
|
|
71
|
+
agent_command: "claude --permission-mode plan",
|
|
72
|
+
agent_trigger_chord: "ctrl-shift-cmd-c",
|
|
73
|
+
repos: [],
|
|
74
|
+
repo_overrides: {}
|
|
75
|
+
};
|
|
76
|
+
var DEFAULT_CONFIG_DIR = envPaths("wt").config;
|
|
77
|
+
function getConfigFilePath(cwd) {
|
|
78
|
+
const dir = cwd ?? DEFAULT_CONFIG_DIR;
|
|
79
|
+
return path2.join(dir, "config.json");
|
|
80
|
+
}
|
|
81
|
+
function createStore(cwd) {
|
|
82
|
+
try {
|
|
83
|
+
return new Conf({
|
|
84
|
+
projectName: "wt",
|
|
85
|
+
defaults: DEFAULT_CONFIG,
|
|
86
|
+
...cwd ? { cwd } : {}
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const configPath = getConfigFilePath(cwd);
|
|
90
|
+
console.error(
|
|
91
|
+
`Error reading config file: ${configPath}
|
|
92
|
+
${error instanceof Error ? error.message : error}`
|
|
93
|
+
);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function getGlobalConfig(store = createStore()) {
|
|
98
|
+
return store.store;
|
|
99
|
+
}
|
|
100
|
+
function getEffectiveConfig(repoPath, store = createStore()) {
|
|
101
|
+
const {
|
|
102
|
+
repos: _repos,
|
|
103
|
+
repo_overrides,
|
|
104
|
+
...repoFields
|
|
105
|
+
} = getGlobalConfig(store);
|
|
106
|
+
const override = repo_overrides[repoPath] ?? {};
|
|
107
|
+
return { ...repoFields, ...override };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
getConfigFilePath,
|
|
112
|
+
createStore,
|
|
113
|
+
getGlobalConfig,
|
|
114
|
+
getEffectiveConfig
|
|
115
|
+
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createStore,
|
|
4
4
|
getGlobalConfig
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FNAMNRUH.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/git.ts
|
|
8
8
|
import { execFileSync } from "child_process";
|
|
@@ -11,11 +11,13 @@ import path from "path";
|
|
|
11
11
|
function getRepoRoot(cwd = process.cwd()) {
|
|
12
12
|
try {
|
|
13
13
|
const realCwd = realpathSync(cwd);
|
|
14
|
-
|
|
14
|
+
const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
|
|
15
15
|
cwd: realCwd,
|
|
16
16
|
encoding: "utf8",
|
|
17
17
|
stdio: "pipe"
|
|
18
|
-
})
|
|
18
|
+
});
|
|
19
|
+
const firstLine = output.trim().split("\n")[0];
|
|
20
|
+
return realpathSync(firstLine.slice("worktree ".length));
|
|
19
21
|
} catch {
|
|
20
22
|
throw new Error("Not in a git repository");
|
|
21
23
|
}
|
|
@@ -197,6 +199,33 @@ function getRegisteredRepos(store = createStore()) {
|
|
|
197
199
|
return getGlobalConfig(store).repos;
|
|
198
200
|
}
|
|
199
201
|
|
|
202
|
+
// src/lib/setup.ts
|
|
203
|
+
import { spawn as spawn2 } from "child_process";
|
|
204
|
+
async function runCommands(commands, cwd) {
|
|
205
|
+
for (const command of commands) {
|
|
206
|
+
const result = await runCommand(command, cwd);
|
|
207
|
+
if (!result.success) {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
failedCommand: command,
|
|
211
|
+
exitCode: result.exitCode
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { success: true };
|
|
216
|
+
}
|
|
217
|
+
function runCommand(command, cwd) {
|
|
218
|
+
return new Promise((resolve) => {
|
|
219
|
+
const child = spawn2(command, { cwd, stdio: "inherit", shell: true });
|
|
220
|
+
child.on("error", () => {
|
|
221
|
+
resolve({ success: false, exitCode: void 0 });
|
|
222
|
+
});
|
|
223
|
+
child.on("close", (code) => {
|
|
224
|
+
resolve({ success: code === 0, exitCode: code ?? void 0 });
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
200
229
|
// src/lib/tui.ts
|
|
201
230
|
import path2 from "path";
|
|
202
231
|
import Fuse from "fuse.js";
|
|
@@ -224,37 +253,57 @@ function shortenPath(p) {
|
|
|
224
253
|
const home = process.env.HOME ?? "";
|
|
225
254
|
return home && p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
226
255
|
}
|
|
227
|
-
function
|
|
228
|
-
const
|
|
256
|
+
function buildListLayout(items, selectedIndex, query, mode) {
|
|
257
|
+
const header = [];
|
|
229
258
|
if (mode === "global") {
|
|
230
|
-
|
|
259
|
+
header.push(
|
|
231
260
|
pc.dim("\u2139 Not in a git repository \u2014 showing all registered worktrees")
|
|
232
261
|
);
|
|
233
|
-
|
|
262
|
+
header.push("");
|
|
234
263
|
}
|
|
235
|
-
|
|
236
|
-
|
|
264
|
+
header.push(pc.cyan(`> ${query}_`));
|
|
265
|
+
const body = [];
|
|
266
|
+
const itemSpans = [];
|
|
237
267
|
const groups = groupByRepo(items);
|
|
238
268
|
let i = 0;
|
|
239
269
|
for (const [repoPath, groupItems] of groups) {
|
|
240
|
-
|
|
270
|
+
body.push(pc.bold(path2.basename(repoPath).toUpperCase()));
|
|
241
271
|
for (const item of groupItems) {
|
|
272
|
+
const start = body.length;
|
|
242
273
|
const cursor = i === selectedIndex ? pc.cyan("\u25B6") : " ";
|
|
243
274
|
const branchLabel = item.isCurrent ? pc.dim(`${item.branch} (current)`) : pc.white(item.branch);
|
|
244
275
|
const pathLabel = pc.dim(shortenPath(item.path));
|
|
245
|
-
|
|
276
|
+
body.push(` ${cursor} ${branchLabel} ${pathLabel}`);
|
|
246
277
|
if (item.lastCommit) {
|
|
247
|
-
|
|
278
|
+
body.push(` ${pc.dim(item.lastCommit)}`);
|
|
248
279
|
}
|
|
280
|
+
itemSpans[i] = { start, end: body.length - 1 };
|
|
249
281
|
i++;
|
|
250
282
|
}
|
|
251
283
|
}
|
|
252
|
-
lines.push("");
|
|
253
284
|
const createHint = mode === "repo" ? " \xB7 C create" : "";
|
|
254
|
-
|
|
285
|
+
const footer = [
|
|
255
286
|
pc.dim(`\u2195 navigate \xB7 Enter open \xB7 D delete${createHint} \xB7 Q quit`)
|
|
256
|
-
|
|
257
|
-
return
|
|
287
|
+
];
|
|
288
|
+
return { header, body, footer, itemSpans };
|
|
289
|
+
}
|
|
290
|
+
function clampScroll(offset, span, viewportHeight, bodyLength) {
|
|
291
|
+
const maxOffset = Math.max(0, bodyLength - viewportHeight);
|
|
292
|
+
let next = offset;
|
|
293
|
+
if (span.start < next) next = span.start;
|
|
294
|
+
if (span.end >= next + viewportHeight) next = span.end - viewportHeight + 1;
|
|
295
|
+
return Math.max(0, Math.min(next, maxOffset));
|
|
296
|
+
}
|
|
297
|
+
function composeView(layout, offset, viewportHeight) {
|
|
298
|
+
const { header, body, footer } = layout;
|
|
299
|
+
const visible = body.slice(offset, offset + viewportHeight);
|
|
300
|
+
while (visible.length < viewportHeight) visible.push("");
|
|
301
|
+
const topSlot = offset > 0 ? pc.dim(" \u2191 more") : "";
|
|
302
|
+
const bottomSlot = offset + viewportHeight < body.length ? pc.dim(" \u2193 more") : "";
|
|
303
|
+
return [...header, topSlot, ...visible, bottomSlot, ...footer].join("\n");
|
|
304
|
+
}
|
|
305
|
+
function fixedHeight(layout) {
|
|
306
|
+
return layout.header.length + layout.footer.length + 2;
|
|
258
307
|
}
|
|
259
308
|
function setupRawMode() {
|
|
260
309
|
process.stdin.setRawMode(true);
|
|
@@ -406,10 +455,21 @@ async function runBranchInput(repoRoot) {
|
|
|
406
455
|
async function runInteractiveList(allItems, mode, handlers) {
|
|
407
456
|
let query = "";
|
|
408
457
|
let selectedIndex = 0;
|
|
458
|
+
let scrollOffset = 0;
|
|
409
459
|
let filtered = allItems;
|
|
410
460
|
const render = () => {
|
|
461
|
+
const rows = process.stdout.rows ?? 24;
|
|
462
|
+
const layout = buildListLayout(filtered, selectedIndex, query, mode);
|
|
463
|
+
const viewport = Math.max(1, rows - fixedHeight(layout));
|
|
464
|
+
const span = layout.itemSpans[selectedIndex] ?? { start: 0, end: 0 };
|
|
465
|
+
scrollOffset = clampScroll(
|
|
466
|
+
scrollOffset,
|
|
467
|
+
span,
|
|
468
|
+
viewport,
|
|
469
|
+
layout.body.length
|
|
470
|
+
);
|
|
411
471
|
process.stdout.write("\x1B[2J\x1B[H");
|
|
412
|
-
process.stdout.write(
|
|
472
|
+
process.stdout.write(composeView(layout, scrollOffset, viewport));
|
|
413
473
|
};
|
|
414
474
|
setupRawMode();
|
|
415
475
|
render();
|
|
@@ -418,11 +478,13 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
418
478
|
const attachListener = () => {
|
|
419
479
|
if (!listenerActive) {
|
|
420
480
|
process.stdin.on("data", onData);
|
|
481
|
+
process.stdout.on("resize", render);
|
|
421
482
|
listenerActive = true;
|
|
422
483
|
}
|
|
423
484
|
};
|
|
424
485
|
const detachListener = () => {
|
|
425
486
|
process.stdin.removeListener("data", onData);
|
|
487
|
+
process.stdout.removeListener("resize", render);
|
|
426
488
|
listenerActive = false;
|
|
427
489
|
};
|
|
428
490
|
const onData = async (key) => {
|
|
@@ -484,11 +546,13 @@ async function runInteractiveList(allItems, mode, handlers) {
|
|
|
484
546
|
query = query.slice(0, -1);
|
|
485
547
|
filtered = filterItems(allItems, query);
|
|
486
548
|
selectedIndex = 0;
|
|
549
|
+
scrollOffset = 0;
|
|
487
550
|
render();
|
|
488
551
|
} else if (key.length === 1 && key >= " ") {
|
|
489
552
|
query += key;
|
|
490
553
|
filtered = filterItems(allItems, query);
|
|
491
554
|
selectedIndex = 0;
|
|
555
|
+
scrollOffset = 0;
|
|
492
556
|
render();
|
|
493
557
|
}
|
|
494
558
|
} catch (err) {
|
|
@@ -514,6 +578,7 @@ export {
|
|
|
514
578
|
openIde,
|
|
515
579
|
registerRepo,
|
|
516
580
|
getRegisteredRepos,
|
|
581
|
+
runCommands,
|
|
517
582
|
runRepoPicker,
|
|
518
583
|
runBranchInput,
|
|
519
584
|
runInteractiveList
|
package/dist/cli.js
CHANGED
|
@@ -3,29 +3,35 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
var program = new Command();
|
|
6
|
-
program.name("wt").description("Git worktree manager").version("0.
|
|
7
|
-
const { runList } = await import("./list-
|
|
6
|
+
program.name("wt").description("Git worktree manager").version("0.4.0-pr17.g3b7d0a6").action(async () => {
|
|
7
|
+
const { runList } = await import("./list-WC5GM3PI.js");
|
|
8
8
|
await runList();
|
|
9
9
|
});
|
|
10
10
|
program.command("create [branch]").description("Create a new worktree").action(async (branch) => {
|
|
11
|
-
const { createWorktree } = await import("./create-
|
|
11
|
+
const { createWorktree } = await import("./create-WRMXQIGN.js");
|
|
12
12
|
await createWorktree(branch);
|
|
13
13
|
});
|
|
14
|
-
program.command("agent <branch> <plan_prompt>").description("Create a worktree and auto-start an AI agent in Zed (macOS)").
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
program.command("agent <branch> <plan_prompt>").description("Create a worktree and auto-start an AI agent in Zed (macOS)").option(
|
|
15
|
+
"--mode <mode>",
|
|
16
|
+
"Claude Code permission mode (default, plan, auto, etc.)",
|
|
17
|
+
"plan"
|
|
18
|
+
).action(
|
|
19
|
+
async (branch, planPrompt, options) => {
|
|
20
|
+
const { createAgentWorktree } = await import("./agent-CIGIZGIS.js");
|
|
21
|
+
await createAgentWorktree(branch, planPrompt, { mode: options.mode });
|
|
22
|
+
}
|
|
23
|
+
);
|
|
18
24
|
program.command("config").description("Open the config file in $EDITOR").option("--path", "Print the config file path and exit").action(async (options) => {
|
|
19
25
|
if (options.path) {
|
|
20
|
-
const { printConfigPath } = await import("./config-
|
|
26
|
+
const { printConfigPath } = await import("./config-DBM7HJE4.js");
|
|
21
27
|
printConfigPath();
|
|
22
28
|
} else {
|
|
23
|
-
const { openConfig } = await import("./config-
|
|
29
|
+
const { openConfig } = await import("./config-DBM7HJE4.js");
|
|
24
30
|
openConfig();
|
|
25
31
|
}
|
|
26
32
|
});
|
|
27
33
|
program.command("skill").description("Print the wt skill file to stdout").action(async () => {
|
|
28
|
-
const { printSkill } = await import("./skill-
|
|
34
|
+
const { printSkill } = await import("./skill-FEUVQMK6.js");
|
|
29
35
|
printSkill();
|
|
30
36
|
});
|
|
31
37
|
await program.parseAsync(process.argv);
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
getConfigFilePath
|
|
4
|
+
} from "./chunk-FNAMNRUH.js";
|
|
5
5
|
|
|
6
6
|
// src/commands/config.ts
|
|
7
7
|
import { spawn } from "child_process";
|
|
8
|
-
function
|
|
9
|
-
|
|
8
|
+
function printConfigPath(cwd) {
|
|
9
|
+
const configPath = getConfigFilePath(cwd);
|
|
10
|
+
console.log(configPath);
|
|
10
11
|
}
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
function openConfig(store = createStore()) {
|
|
15
|
-
const configPath = store.path;
|
|
12
|
+
function openConfig(cwd) {
|
|
13
|
+
const configPath = getConfigFilePath(cwd);
|
|
16
14
|
console.log(`Config: ${configPath}`);
|
|
17
15
|
const editor = process.env.EDITOR ?? "nano";
|
|
18
16
|
const child = spawn(editor, [configPath], { stdio: "inherit" });
|
|
@@ -23,7 +21,6 @@ function openConfig(store = createStore()) {
|
|
|
23
21
|
child.on("close", (code) => process.exit(code ?? 0));
|
|
24
22
|
}
|
|
25
23
|
export {
|
|
26
|
-
getConfigPath,
|
|
27
24
|
openConfig,
|
|
28
25
|
printConfigPath
|
|
29
26
|
};
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
openConfiguredIde,
|
|
5
5
|
prepareWorktree,
|
|
6
6
|
promptExistingWorktree
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-BSMGKL27.js";
|
|
8
|
+
import "./chunk-XM4EABZV.js";
|
|
9
|
+
import "./chunk-FNAMNRUH.js";
|
|
10
10
|
export {
|
|
11
11
|
createWorktree,
|
|
12
12
|
openConfiguredIde,
|
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
openIde,
|
|
8
8
|
registerRepo,
|
|
9
9
|
removeWorktree,
|
|
10
|
+
runCommands,
|
|
10
11
|
runInteractiveList
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XM4EABZV.js";
|
|
12
13
|
import {
|
|
13
14
|
createStore,
|
|
14
15
|
getEffectiveConfig
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-FNAMNRUH.js";
|
|
16
17
|
|
|
17
18
|
// src/commands/list.ts
|
|
18
19
|
import * as clack from "@clack/prompts";
|
|
@@ -60,6 +61,20 @@ async function runList(options = {}) {
|
|
|
60
61
|
message: `Remove worktree ${pc.bold(item.branch)}? This cannot be undone.`
|
|
61
62
|
});
|
|
62
63
|
if (clack.isCancel(confirmed) || !confirmed) return false;
|
|
64
|
+
const config = getEffectiveConfig(item.repoRoot, store);
|
|
65
|
+
if (config.teardown_commands.length > 0) {
|
|
66
|
+
console.log(pc.dim("Running teardown commands..."));
|
|
67
|
+
const result = await runCommands(config.teardown_commands, item.path);
|
|
68
|
+
if (!result.success) {
|
|
69
|
+
clack.log.warn(
|
|
70
|
+
`Teardown command failed: ${result.failedCommand} (exit code ${result.exitCode})`
|
|
71
|
+
);
|
|
72
|
+
const proceed = await clack.confirm({
|
|
73
|
+
message: `Delete ${pc.bold(item.branch)} anyway?`
|
|
74
|
+
});
|
|
75
|
+
if (clack.isCancel(proceed) || !proceed) return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
63
78
|
try {
|
|
64
79
|
removeWorktree(item.repoRoot, item.path);
|
|
65
80
|
console.log(pc.green(`\u2713 Removed ${item.branch}`));
|
|
@@ -110,7 +125,7 @@ ${dirty.map((f) => ` ${f}`).join("\n")}`
|
|
|
110
125
|
},
|
|
111
126
|
onCreate: async () => {
|
|
112
127
|
if (repoRoot) {
|
|
113
|
-
const { createWorktree } = await import("./create-
|
|
128
|
+
const { createWorktree } = await import("./create-WRMXQIGN.js");
|
|
114
129
|
await createWorktree(void 0, { cwd: repoRoot, store });
|
|
115
130
|
}
|
|
116
131
|
}
|
|
@@ -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> [--mode <mode>]`\n\nCreate a worktree (same as `wt create`) **and** auto-start an AI agent in Zed\'s\nintegrated terminal, pre-filled with `<plan_prompt>` and left interactive for\nyou to take over.\n\n```bash\nwt agent feature/login \'Read the codebase, then propose a plan for login.\'\nwt agent feature/fix \'Fix the bug in payment processing\' --mode auto\nwt agent refactor/api \'Refactor the API layer\' --mode default\n```\n\nThe `--mode` flag sets Claude Code\'s permission mode (defaults to `plan`):\n\n- `default` \u2014 Standard interactive mode with approval for each action\n- `acceptEdits` \u2014 Allow file changes but keep command execution controlled\n- `plan` \u2014 Architecture-first mode with no surprise mutations (default)\n- `auto` \u2014 Claude\'s safety model makes decisions instead of prompting\n- `dontAsk` \u2014 Minimal interruptions in trusted environments\n- `bypassPermissions` \u2014 Skip all permission checks (dangerous, CI/sandbox only)\n\nIt writes a temporary `.zed/tasks.json` running\n`<agent_command> --permission-mode <mode> \'<plan_prompt>\'`, ensures a global Zed keymap chord\n(`agent_trigger_chord`) spawns that task, opens Zed, presses the chord via\n`osascript`, then removes the temporary task so the repo is left clean.\n\n**macOS + Zed only.** Requires Accessibility permission for the app that runs\n`wt` (Zed itself, when run from its integrated terminal). If it isn\'t granted,\n`wt agent` opens the _Privacy & Security \u2192 Accessibility_ settings pane and waits\nfor you to grant it and confirm, then retries automatically. On other platforms\n(or when `ide` is not `zed`) the worktree is still created and opened, but the\nagent is not auto-started.\n\nOver SSH it still works, provided the same user has an active graphical login on\nthe Mac: the keystroke is run inside the GUI session via Launch Services\n(`open -a Terminal` briefly flashes a Terminal window). Grant Accessibility to\nTerminal (not Zed) the first time. With no one logged in graphically there is\nnothing to drive, so it falls back to the manual "press the chord in Zed"\nmessage.\n\nIf the worktree path already exists, `wt agent` prompts you to **open it in the\nIDE**, **open it and start the agent**, or **quit** \u2014 instead of erroring. (In a\nnon-interactive shell it errors with a non-zero exit instead of prompting.)\n\n### `wt config`\n\nOpen the global config file in `$EDITOR` (defaults to `nano`).\n\n```bash\nwt config # open in editor\nwt config --path # print config file path only\n```\n\n### `wt skill`\n\nPrint this skill file to stdout. Useful for piping to agents or copying to a project.\n\n## Configuration\n\nConfig is stored as JSON. Get the path with `wt config --path`.\n\n### Schema\n\n| Key | Type | Default | Description |\n| --------------------- | ---------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `worktree_path` | `string` | `"../"` | Where to place new worktrees, relative to the repo root |\n| `base_branch` | `string` | `"origin/main"` | Branch to base new worktrees on |\n| `setup_commands` | `string[]` | `[]` | Commands to run in a new worktree after creation (e.g. `["npm install"]`) |\n| `teardown_commands` | `string[]` | `[]` | Commands to run in a worktree just before it is deleted (e.g. `["docker compose down -v"]`); on failure you are prompted whether to delete anyway |\n| `ide` | `string` | `"zed"` | IDE command to open worktrees with |\n| `ide_open_args` | `string[]` | `["-n"]` | Arguments passed to the IDE command |\n| `agent_command` | `string` | `"claude --permission-mode plan"` | Base command `wt agent` runs in Zed; any `--permission-mode` flag is replaced by the `--mode` option (defaults to `plan`), then `<plan_prompt>` is appended single-quoted |\n| `agent_trigger_chord` | `string` | `"ctrl-shift-cmd-c"` | Zed keymap chord `wt agent` installs/presses to spawn the agent task |\n| `repos` | `string[]` | `[]` | Registered repo paths (auto-populated on first use) |\n| `repo_overrides` | `object` | `{}` | Per-repo config overrides (see below) |\n\n### Per-repo overrides\n\nOverride any field (`worktree_path`, `base_branch`, `setup_commands`, `teardown_commands`, `ide`, `ide_open_args`, `agent_command`, `agent_trigger_chord`) for a specific repo:\n\n```json\n{\n "base_branch": "origin/main",\n "ide": "zed",\n "repo_overrides": {\n "/path/to/my-repo": {\n "base_branch": "origin/develop",\n "setup_commands": ["npm install", "npm run build"]\n }\n }\n}\n```\n\n## Common workflows\n\n### Create a worktree for a new feature\n\n```bash\ncd /path/to/repo\nwt create feature/my-branch\n```\n\n### Configure setup commands for a repo\n\n```bash\nwt config\n# Then add to the JSON:\n# "repo_overrides": {\n# "/path/to/repo": {\n# "setup_commands": ["npm install"]\n# }\n# }\n```\n\n### Browse all worktrees across repos\n\nRun `wt` from any directory outside a git repo to see worktrees from all registered repos.\n');
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
printSkill
|
|
9
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-IQWENLPW.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/lib/config.ts
|
|
4
|
-
import Conf from "conf";
|
|
5
|
-
var DEFAULT_CONFIG = {
|
|
6
|
-
worktree_path: "../",
|
|
7
|
-
base_branch: "origin/main",
|
|
8
|
-
setup_commands: [],
|
|
9
|
-
ide: "zed",
|
|
10
|
-
ide_open_args: ["-n"],
|
|
11
|
-
agent_command: "claude --permission-mode plan",
|
|
12
|
-
agent_trigger_chord: "ctrl-shift-cmd-c",
|
|
13
|
-
repos: [],
|
|
14
|
-
repo_overrides: {}
|
|
15
|
-
};
|
|
16
|
-
function createStore(cwd) {
|
|
17
|
-
return new Conf({
|
|
18
|
-
projectName: "wt",
|
|
19
|
-
defaults: DEFAULT_CONFIG,
|
|
20
|
-
...cwd ? { cwd } : {}
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function getGlobalConfig(store = createStore()) {
|
|
24
|
-
return store.store;
|
|
25
|
-
}
|
|
26
|
-
function getEffectiveConfig(repoPath, store = createStore()) {
|
|
27
|
-
const {
|
|
28
|
-
repos: _repos,
|
|
29
|
-
repo_overrides,
|
|
30
|
-
...repoFields
|
|
31
|
-
} = getGlobalConfig(store);
|
|
32
|
-
const override = repo_overrides[repoPath] ?? {};
|
|
33
|
-
return { ...repoFields, ...override };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export {
|
|
37
|
-
createStore,
|
|
38
|
-
getGlobalConfig,
|
|
39
|
-
getEffectiveConfig
|
|
40
|
-
};
|
package/dist/skill-RR6MNLWH.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/commands/skill.ts
|
|
4
|
-
function printSkill() {
|
|
5
|
-
console.log('---\nname: wt-worktree-manager\ndescription: Use the wt CLI to create, browse, open, and delete git worktrees across repos. Use when the user asks to manage worktrees, create isolated branches, or configure worktree defaults.\n---\n\n# wt \u2014 Git Worktree Manager\n\n`wt` is a CLI for managing git worktrees. It provides an interactive TUI to browse, create, open in your IDE, and delete worktrees across multiple repos.\n\n## Commands\n\n### `wt` (no subcommand)\n\nLaunch the interactive TUI. Shows worktrees for the current repo (repo mode) or all registered repos (global mode, when run outside a repo).\n\n**Keybindings in the TUI:**\n\n- Arrow keys / `j`/`k` \u2014 navigate\n- `Enter` \u2014 open worktree in IDE\n- `d` \u2014 delete worktree\n- `c` \u2014 create new worktree (repo mode only)\n- `/` \u2014 search\n- `q` / `Esc` \u2014 quit\n\n### `wt create [branch]`\n\nCreate a new worktree. If `branch` is omitted, prompts interactively.\n\nThe worktree is created as a sibling directory to the repo: `<parent>/<repo-name>-<branch-name>`.\n\nAfter creation, `wt` runs any configured `setup_commands` and opens the worktree in your IDE.\n\nIf the worktree path already exists, `wt create` doesn\'t error \u2014 it prompts you\nto **open it in the IDE** or **quit**. (In a non-interactive shell it errors\nwith a non-zero exit instead of prompting.)\n\n### `wt agent <branch> <plan_prompt>`\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
|
-
};
|