wtcode 0.3.0__tar.gz → 0.5.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wtcode
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Launch your favorite code tool in a git worktree
5
5
  Author-email: Jaeho Shin <netj@sparcs.org>
6
6
  License-Expression: MIT
@@ -61,6 +61,7 @@ wtcode --exec CMD [CMD-ARGS...]
61
61
 
62
62
  - **`BRANCH`** -- Git branch or worktree name. If omitted and [fzf](https://github.com/junegunn/fzf) is available, interactively select one. Surround with `:` to create a new branch (use `:::name` or `name:::` to avoid fzf matching the colons).
63
63
  - **`CMD`** -- Command to launch in the worktree. Defaults to `$WTCODE_CMD`, or the first available of: `claude`, `aider`, `codex`, `$SHELL`.
64
+ When `CMD` resolves to plain `claude` (no extra args) in a worktree that already existed, wtcode launches `claude /resume` so it opens straight into your last session there. Press `Esc` in the picker to start a new session instead.
64
65
  - **`--exec`** -- Skip the branch argument; select interactively via fzf, then launch `CMD`.
65
66
  - **`--help`** / **`--version`** -- Show help or version info.
66
67
 
@@ -81,6 +82,7 @@ WTCODE_CMD=cursor wtcode feature # use cursor as default tool
81
82
  | Variable | Description |
82
83
  |---|---|
83
84
  | `WTCODE_CMD` | Default tool to launch (e.g., `claude`, `lazygit`, `vim`, `cursor`) |
85
+ | `WTCODE_USE_CURRENT_BRANCH` | When creating a brand-new branch, fork it from the current HEAD instead of the default: `origin/HEAD` (falling back to `origin/main`, then `origin/master`) |
84
86
  | `WTCODE_DEBUG` | Enable debug tracing when set to any value |
85
87
  | `GIT_WORKTREE_ROOT` | Override the directory where worktrees are created |
86
88
 
@@ -43,6 +43,7 @@ wtcode --exec CMD [CMD-ARGS...]
43
43
 
44
44
  - **`BRANCH`** -- Git branch or worktree name. If omitted and [fzf](https://github.com/junegunn/fzf) is available, interactively select one. Surround with `:` to create a new branch (use `:::name` or `name:::` to avoid fzf matching the colons).
45
45
  - **`CMD`** -- Command to launch in the worktree. Defaults to `$WTCODE_CMD`, or the first available of: `claude`, `aider`, `codex`, `$SHELL`.
46
+ When `CMD` resolves to plain `claude` (no extra args) in a worktree that already existed, wtcode launches `claude /resume` so it opens straight into your last session there. Press `Esc` in the picker to start a new session instead.
46
47
  - **`--exec`** -- Skip the branch argument; select interactively via fzf, then launch `CMD`.
47
48
  - **`--help`** / **`--version`** -- Show help or version info.
48
49
 
@@ -63,6 +64,7 @@ WTCODE_CMD=cursor wtcode feature # use cursor as default tool
63
64
  | Variable | Description |
64
65
  |---|---|
65
66
  | `WTCODE_CMD` | Default tool to launch (e.g., `claude`, `lazygit`, `vim`, `cursor`) |
67
+ | `WTCODE_USE_CURRENT_BRANCH` | When creating a brand-new branch, fork it from the current HEAD instead of the default: `origin/HEAD` (falling back to `origin/main`, then `origin/master`) |
66
68
  | `WTCODE_DEBUG` | Enable debug tracing when set to any value |
67
69
  | `GIT_WORKTREE_ROOT` | Override the directory where worktrees are created |
68
70
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wtcode"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "Launch your favorite code tool in a git worktree"
9
9
  license = "MIT"
10
10
  readme = "README.md"
@@ -1,3 +1,3 @@
1
1
  """wtcode -- launch your favorite code tool in a git worktree."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.5.0"
@@ -5,9 +5,21 @@
5
5
  # Created: 2026-02-03
6
6
  set -eu
7
7
  shopt -s extglob
8
+
9
+ # declare all WTCODE_* env vars and their defaults in one place
10
+ : ${WTCODE_CMD:=}
11
+ : ${WTCODE_USE_CURRENT_BRANCH:=false}
12
+ : ${WTCODE_TMUX_MODE:=send-keys}
13
+ : ${WTCODE_TERMINAL_MODE:=send-keys}
14
+ : ${WTCODE_DEBUG:=}
15
+
8
16
  ${WTCODE_DEBUG:+set -x}
9
17
 
10
18
  --msg() { echo "wtcode: $*" >&2; }
19
+ @q() {
20
+ local quoted=$({ set +x; } 2>/dev/null; exec 2>&1; PS4=; set -x; : "$@")
21
+ echo "${quoted:2}" # lstrip the ': ' prefix to get the compact bash-quoted form
22
+ }
11
23
  --sanitize-branch-name() {
12
24
  printf '%s' "$1" |
13
25
  tr '[:upper:]' '[:lower:]' | # lowercase
@@ -16,7 +28,7 @@ ${WTCODE_DEBUG:+set -x}
16
28
  sed 's/^-//; s/-$//' # trim leading/trailing hyphens
17
29
  }
18
30
 
19
- WTCODE_VERSION=0.3.0
31
+ WTCODE_VERSION=0.5.0
20
32
  --version() { echo "wtcode $WTCODE_VERSION"; }
21
33
  --help() {
22
34
  cat <<USAGE
@@ -38,11 +50,21 @@ Usage: wtcode [BRANCH] [CMD [CMD-ARGS...]]
38
50
 
39
51
  Environment variables:
40
52
  WTCODE_CMD Default tool to launch (e.g., claude, lazygit, vim)
53
+ WTCODE_USE_CURRENT_BRANCH
54
+ When creating a brand-new branch, fork it from the
55
+ current HEAD instead of the default: origin/HEAD
56
+ (falling back to origin/main, then origin/master)
41
57
  WTCODE_TMUX_MODE When in tmux, control how the tool is launched:
42
58
  send-keys - send command to current pane (default in tmux)
43
59
  split-window - create split pane with send-keys
44
60
  new-window - create new window with send-keys
45
61
  exec - exec the tool directly (opt out of send-keys)
62
+ WTCODE_TERMINAL_MODE Outside tmux, control how the tool is launched:
63
+ send-keys - reuse the current tab/window via AppleScript
64
+ (default; macOS only). Terminal.app and Ghostty
65
+ 1.3+ use their own scripting support; any other
66
+ terminal falls back to simulated keystrokes
67
+ exec - exec the tool directly (opt out of send-keys)
46
68
  WTCODE_DEBUG Enable debug tracing when set
47
69
  GIT_WORKTREE_ROOT Override the directory where worktrees are created
48
70
 
@@ -58,17 +80,39 @@ USAGE
58
80
 
59
81
  # commands to try as default, in order of preference
60
82
  WTCODE_CMDS_TO_TRY=(
61
- ${WTCODE_CMD:-}
83
+ $WTCODE_CMD
62
84
  claude
63
85
  aider
64
86
  codex
65
87
  )
66
88
 
89
+ ###############################################################################
90
+ ## --default-base-ref -- ref to fork a brand-new branch from
91
+ ###############################################################################
92
+ # defaults to origin/HEAD (falling back to origin/main, then origin/master),
93
+ # so new worktrees start from the repo's default branch instead of whatever
94
+ # happens to be checked out where wtcode was invoked. set
95
+ # WTCODE_USE_CURRENT_BRANCH=true to restore the old behavior of forking from
96
+ # the current HEAD. only consults local remote-tracking refs -- never
97
+ # touches the network.
98
+ --default-base-ref() {
99
+ if [[ $WTCODE_USE_CURRENT_BRANCH == true ]]; then
100
+ git rev-parse HEAD
101
+ return
102
+ fi
103
+ local ref
104
+ for ref in origin/HEAD origin/main origin/master; do
105
+ git rev-parse --verify -q "$ref" &>/dev/null && { echo "$ref"; return; }
106
+ done
107
+ git rev-parse HEAD
108
+ }
109
+
67
110
  ###############################################################################
68
111
  ## --enter-git-worktree -- select branch and create/switch worktree
69
112
  ###############################################################################
70
113
  --enter-git-worktree() {
71
114
  local branch_name=
115
+ WTCODE_WORKTREE_EXISTED=false
72
116
 
73
117
  # 1. determine which branch/worktree to use
74
118
  if [[ $# -gt 0 ]]; then
@@ -118,9 +162,13 @@ WTCODE_CMDS_TO_TRY=(
118
162
  printf '%s\t%s %s\t%s %s %s%s\n' \
119
163
  "$branch" "$ind" "$name_shown" "$date_shown" "$hash_shown" "$upstream_shown" "$subject"
120
164
  done |
121
- fzf --ansi --color --tmux --print-query --delimiter=$'\t' --with-nth=2 --nth=1 \
165
+ fzf --ansi --color --tmux 90%,70% --print-query --delimiter=$'\t' --with-nth=2 --nth=1 \
122
166
  --preview 'echo {3}' --preview-window 'down:2:wrap' \
123
- --prompt 'wtcode: select worktree/branch (prefix : to create new) > ' |
167
+ --header "$(
168
+ echo '[wtcode] Select the worktree/branch or create new to launch your code tool in it.'
169
+ echo ' Typing colons `:` avoids selecting an existing one to create new with similar names.'
170
+ )" \
171
+ --prompt '> ' |
124
172
  cut -f1 |
125
173
  tail -1
126
174
  )
@@ -172,6 +220,7 @@ WTCODE_CMDS_TO_TRY=(
172
220
  if [[ -n $existing_worktree ]]; then
173
221
  --msg "branch '$branch_name' is checked out at: $existing_worktree"
174
222
  cd "$existing_worktree"
223
+ WTCODE_WORKTREE_EXISTED=true
175
224
  return 0
176
225
  fi
177
226
  fi
@@ -207,6 +256,7 @@ WTCODE_CMDS_TO_TRY=(
207
256
 
208
257
  if [[ -e "$worktree_path"/.git ]]; then
209
258
  --msg "using existing worktree: $worktree_path"
259
+ WTCODE_WORKTREE_EXISTED=true
210
260
  elif [[ -n ${remote_branch-} ]] && ! git rev-parse --verify "refs/heads/$branch_name" &>/dev/null; then
211
261
  # remote branch: create local tracking branch in a new worktree
212
262
  --msg "creating worktree for remote branch: $remote_branch"
@@ -217,9 +267,14 @@ WTCODE_CMDS_TO_TRY=(
217
267
  --msg "creating worktree for branch: $branch_name"
218
268
  git worktree add -B "$branch_name" "$worktree_path" "$(git rev-parse "$branch_name")"
219
269
  else
220
- # fork the current HEAD and create the new worktree
221
- --msg "creating worktree with new branch: $branch_name"
222
- git worktree add -b "$branch_name" "$worktree_path" "$(git rev-parse HEAD)"
270
+ # fork a base ref and create the new worktree: defaults to origin/HEAD
271
+ # (falling back to origin/main, origin/master, then the current HEAD);
272
+ # set WTCODE_USE_CURRENT_BRANCH=true to always fork from the current
273
+ # branch instead
274
+ local base_ref
275
+ base_ref=$(--default-base-ref)
276
+ --msg "creating worktree with new branch: $branch_name (from $base_ref)"
277
+ git worktree add -b "$branch_name" "$worktree_path" "$base_ref"
223
278
  fi
224
279
  cd "$worktree_path"
225
280
 
@@ -295,6 +350,116 @@ WTCODE_CMDS_TO_TRY=(
295
350
  --msg "created .claude/.gitignore"
296
351
  }
297
352
 
353
+ ###############################################################################
354
+ ## --terminal-send-keys -- tmux-like send-keys for GUI terminal apps
355
+ ###############################################################################
356
+ # Reuses the current tab/window in the surrounding terminal app, outside
357
+ # tmux. cd_cmd/run_cmd are sent as two separate lines rather than one
358
+ # `cd X && cmd` line, since the shell's PROMPT_COMMAND (what updates the
359
+ # tab/window title via OSC 7/6) only fires between commands, at the next
360
+ # prompt -- a single `&&` line never shows one, so the title would stay
361
+ # stale for as long as cmd runs.
362
+ #
363
+ # macOS-only: dispatches on $TERM_PROGRAM by looking for a matching
364
+ # --terminal-send-keys--macos--$TERM_PROGRAM function -- add support for
365
+ # another terminal app by just defining one, named to match its
366
+ # $TERM_PROGRAM value exactly. `type osascript` fails (and this returns 1)
367
+ # on any other platform, so this whole mechanism is a no-op there and
368
+ # callers just fall back to exec. Also returns 1 if there's no terminal
369
+ # emulator signal (TERM_PROGRAM/TERMINAL_EMULATOR, as in ~/.zprofile) at all.
370
+ --terminal-send-keys() {
371
+ local worktree_path="$PWD"
372
+ local cd_cmd="cd $(@q "$worktree_path")"
373
+ local run_cmd="$(@q "$@")"
374
+
375
+ # TODO: support Linux/general-unix terminals (and SSH sessions) too --
376
+ # e.g. terminals with OSC 7/6 + a way to inject text (wezterm cli,
377
+ # kitty @ send-text, iterm2's escape sequences, etc). For now this whole
378
+ # mechanism is macOS-only and returns 1 (falls back to exec) elsewhere.
379
+ type osascript &>/dev/null || return 1
380
+
381
+ local termprog=${TERM_PROGRAM-${TERMINAL_EMULATOR-}}
382
+ [[ -n $termprog ]] || return 1
383
+ if type -- "--terminal-send-keys--macos--$termprog" &>/dev/null; then
384
+ "--terminal-send-keys--macos--$termprog" "$cd_cmd" "$run_cmd"
385
+ else
386
+ --terminal-send-keys--macos--system-events "$cd_cmd" "$run_cmd"
387
+ fi
388
+ }
389
+
390
+ # `do script ... in front window`: reuses Terminal's front window via a real
391
+ # Apple Event, not a keystroke. `osascript -` reads the script from stdin;
392
+ # without it, osascript treats argv[0] as a script *file* path.
393
+ --terminal-send-keys--macos--Apple_Terminal() {
394
+ local result
395
+ result=$(osascript - "$@" <<'APPLESCRIPT'
396
+ on run argv
397
+ tell application "Terminal"
398
+ try
399
+ repeat with line_cmd in argv
400
+ do script (line_cmd as text) in front window
401
+ end repeat
402
+ set frontmost of front window to true
403
+ return "OK"
404
+ on error
405
+ return "NOTFOUND"
406
+ end try
407
+ end tell
408
+ end run
409
+ APPLESCRIPT
410
+ ) || return 1
411
+ [[ $result == OK ]]
412
+ }
413
+
414
+ # Ghostty 1.3+ ships a real AppleScript dictionary: `input text ... to
415
+ # term` targets the terminal focused in the front window's selected tab.
416
+ # Falls back to --macos--system-events since this is still a young feature.
417
+ --terminal-send-keys--macos--ghostty() {
418
+ local result rc
419
+ result=$(osascript - "$@" <<'APPLESCRIPT'
420
+ on run argv
421
+ tell application "Ghostty"
422
+ try
423
+ set term to focused terminal of selected tab of front window
424
+ repeat with line_cmd in argv
425
+ input text ((line_cmd as text) & "\n") to term
426
+ end repeat
427
+ return "OK"
428
+ on error
429
+ return "NOTFOUND"
430
+ end try
431
+ end tell
432
+ end run
433
+ APPLESCRIPT
434
+ ); rc=$?
435
+ [[ $rc -eq 0 && $result == OK ]] || --terminal-send-keys--macos--system-events "$@"
436
+ }
437
+
438
+ # Last resort: fake keystrokes via System Events (needs Accessibility
439
+ # permission). No `activate` call -- $TERM_PROGRAM isn't always a real app
440
+ # name, so this relies on the calling terminal still being frontmost.
441
+ --terminal-send-keys--macos--system-events() {
442
+ local result
443
+ result=$(osascript - "$@" <<'APPLESCRIPT'
444
+ on run argv
445
+ tell application "System Events"
446
+ try
447
+ repeat with line_cmd in argv
448
+ keystroke (line_cmd as text)
449
+ key code 36 -- Return
450
+ delay 0.05
451
+ end repeat
452
+ return "OK"
453
+ on error
454
+ return "NOTFOUND"
455
+ end try
456
+ end tell
457
+ end run
458
+ APPLESCRIPT
459
+ ) || return 1
460
+ [[ $result == OK ]]
461
+ }
462
+
298
463
  ###############################################################################
299
464
  ## --launch-code-tool -- resolve and exec the tool in the worktree
300
465
  ###############################################################################
@@ -308,6 +473,14 @@ WTCODE_CMDS_TO_TRY=(
308
473
  [[ $# -gt 0 ]] || set -- "${SHELL:-bash}"
309
474
  fi
310
475
 
476
+ # default to resuming the last session when relaunching claude in a
477
+ # worktree that already existed; press Esc in the picker to start fresh
478
+ # instead. only kicks in when no CMD-ARGS were given, so explicit flags
479
+ # (e.g. `claude --resume`) are never overridden.
480
+ if [[ $1 == claude && $# -eq 1 && ${WTCODE_WORKTREE_EXISTED:-false} == true ]]; then
481
+ set -- "$1" /resume
482
+ fi
483
+
311
484
  [[ $1 == claude ]] && --provision-claude-settings
312
485
 
313
486
  --msg "launching: $*"
@@ -315,17 +488,12 @@ WTCODE_CMDS_TO_TRY=(
315
488
  # when inside tmux, default to send-keys for command history access
316
489
  # set WTCODE_TMUX_MODE=exec to opt out and exec directly
317
490
  if [[ -n ${TMUX:-} ]] && type tmux &>/dev/null; then
318
- local mode=${WTCODE_TMUX_MODE:-send-keys}
491
+ local mode=$WTCODE_TMUX_MODE
319
492
 
320
493
  case $mode in
321
494
  send-keys|split-window|new-window)
322
495
  local worktree_path="$PWD"
323
- # quote each argument for shell safety
324
- local quoted_args=()
325
- for arg in "$@"; do
326
- quoted_args+=("$(printf '%q' "$arg")")
327
- done
328
- local cmd="cd $(printf '%q' "$worktree_path") && ${quoted_args[*]}"
496
+ local cmd="cd $(@q "$worktree_path") && $(@q "$@")"
329
497
 
330
498
  case $mode in
331
499
  send-keys)
@@ -356,7 +524,27 @@ WTCODE_CMDS_TO_TRY=(
356
524
  esac
357
525
  fi
358
526
 
359
- # outside tmux, WTCODE_TMUX_MODE=exec, or unknown mode: exec directly
527
+ # outside tmux: reuse the current tab/window if the terminal app supports
528
+ # it (see --terminal-send-keys). set WTCODE_TERMINAL_MODE=exec to opt out
529
+ if [[ -z ${TMUX:-} ]]; then
530
+ local mode=$WTCODE_TERMINAL_MODE
531
+ case $mode in
532
+ send-keys)
533
+ if --terminal-send-keys "$@"; then
534
+ return 0
535
+ fi
536
+ --msg "could not reuse the current tab/window; falling back to exec"
537
+ ;;
538
+ exec)
539
+ # explicitly opt out of send-keys, fall through to exec
540
+ ;;
541
+ *)
542
+ --msg "unknown WTCODE_TERMINAL_MODE: $mode (expected: send-keys, exec)"
543
+ ;;
544
+ esac
545
+ fi
546
+
547
+ # outside tmux, WTCODE_TMUX_MODE=exec, unsupported terminal, or unknown mode: exec directly
360
548
  exec "$@"
361
549
  }
362
550
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wtcode
3
- Version: 0.3.0
3
+ Version: 0.5.0
4
4
  Summary: Launch your favorite code tool in a git worktree
5
5
  Author-email: Jaeho Shin <netj@sparcs.org>
6
6
  License-Expression: MIT
@@ -61,6 +61,7 @@ wtcode --exec CMD [CMD-ARGS...]
61
61
 
62
62
  - **`BRANCH`** -- Git branch or worktree name. If omitted and [fzf](https://github.com/junegunn/fzf) is available, interactively select one. Surround with `:` to create a new branch (use `:::name` or `name:::` to avoid fzf matching the colons).
63
63
  - **`CMD`** -- Command to launch in the worktree. Defaults to `$WTCODE_CMD`, or the first available of: `claude`, `aider`, `codex`, `$SHELL`.
64
+ When `CMD` resolves to plain `claude` (no extra args) in a worktree that already existed, wtcode launches `claude /resume` so it opens straight into your last session there. Press `Esc` in the picker to start a new session instead.
64
65
  - **`--exec`** -- Skip the branch argument; select interactively via fzf, then launch `CMD`.
65
66
  - **`--help`** / **`--version`** -- Show help or version info.
66
67
 
@@ -81,6 +82,7 @@ WTCODE_CMD=cursor wtcode feature # use cursor as default tool
81
82
  | Variable | Description |
82
83
  |---|---|
83
84
  | `WTCODE_CMD` | Default tool to launch (e.g., `claude`, `lazygit`, `vim`, `cursor`) |
85
+ | `WTCODE_USE_CURRENT_BRANCH` | When creating a brand-new branch, fork it from the current HEAD instead of the default: `origin/HEAD` (falling back to `origin/main`, then `origin/master`) |
84
86
  | `WTCODE_DEBUG` | Enable debug tracing when set to any value |
85
87
  | `GIT_WORKTREE_ROOT` | Override the directory where worktrees are created |
86
88
 
File without changes
File without changes
File without changes