wtcode 0.4.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.
wtcode/__init__.py CHANGED
@@ -1,3 +1,3 @@
1
1
  """wtcode -- launch your favorite code tool in a git worktree."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.7.0"
wtcode/wtcode.sh CHANGED
@@ -5,6 +5,35 @@
5
5
  # Created: 2026-02-03
6
6
  set -eu
7
7
  shopt -s extglob
8
+
9
+ # detect well-known AI coding agents via the env vars they set on their own
10
+ # subprocesses -- send-keys/AppleScript fire into whatever pane/tab/window
11
+ # happens to be current, which is unsafe to do behind an agent's back while
12
+ # it's working (it may be a pane/window the user is watching, or one running
13
+ # an unrelated task). exec is always safe: it just replaces the agent's own
14
+ # child process.
15
+ --running-under-coding-agent() {
16
+ [[ -n ${CLAUDECODE:-} || -n ${CURSOR_AGENT:-} || -n ${GEMINI_CLI:-} ||
17
+ -n ${CODEX_SANDBOX:-} || -n ${CODEX_SANDBOX_NETWORK_DISABLED:-} ]]
18
+ }
19
+
20
+ # declare all WTCODE_* env vars and their defaults in one place
21
+ : ${WTCODE_TOOL:=${WTCODE_CMD:-}} # WTCODE_CMD: deprecated alias
22
+ : ${WTCODE_USE_CURRENT_BRANCH:=false}
23
+ : ${WTCODE_BASE_REF:=} # ref to fork new branches from (also: --base REF)
24
+ : ${WTCODE_BRANCH_SCOPE:=} # initial picker scope: all|worktree|local|remote
25
+ : ${WTCODE_BRANCH_ORDER:=} # initial picker order: recent|name|full
26
+ # where the picker remembers its last state; set to empty to disable
27
+ : ${WTCODE_STATE_FILE=${XDG_STATE_HOME:-$HOME/.local/state}/wtcode/state}
28
+ if --running-under-coding-agent; then
29
+ : ${WTCODE_TMUX_MODE:=exec}
30
+ : ${WTCODE_TERMINAL_MODE:=exec}
31
+ else
32
+ : ${WTCODE_TMUX_MODE:=send-keys}
33
+ : ${WTCODE_TERMINAL_MODE:=send-keys}
34
+ fi
35
+ : ${WTCODE_DEBUG:=}
36
+
8
37
  ${WTCODE_DEBUG:+set -x}
9
38
 
10
39
  --msg() { echo "wtcode: $*" >&2; }
@@ -17,16 +46,18 @@ ${WTCODE_DEBUG:+set -x}
17
46
  tr '[:upper:]' '[:lower:]' | # lowercase
18
47
  sed 's/[^a-z0-9/_-]/-/g' | # replace non-alnum to hyphens
19
48
  sed 's/--*/-/g' | # collapse consecutive hyphens
20
- sed 's/^-//; s/-$//' # trim leading/trailing hyphens
49
+ sed 's|//*|/|g' | # collapse consecutive slashes
50
+ sed 's|-*/-*|/|g' | # no hyphens adjacent to slashes
51
+ sed 's|^[-/]*||; s|[-/]*$||' # trim leading/trailing hyphens/slashes
21
52
  }
22
53
 
23
- WTCODE_VERSION=0.4.0
54
+ WTCODE_VERSION=0.7.0
24
55
  --version() { echo "wtcode $WTCODE_VERSION"; }
25
56
  --help() {
26
57
  cat <<USAGE
27
58
  wtcode $WTCODE_VERSION -- launch a code tool in a git worktree
28
59
 
29
- Usage: wtcode [BRANCH] [CMD [CMD-ARGS...]]
60
+ Usage: wtcode [--base REF] [BRANCH] [CMD [CMD-ARGS...]]
30
61
  wtcode --exec CMD [CMD-ARGS...]
31
62
 
32
63
  BRANCH Git branch or worktree name to switch to.
@@ -35,24 +66,59 @@ Usage: wtcode [BRANCH] [CMD [CMD-ARGS...]]
35
66
  (use multiple colons to avoid fzf matching, e.g., :::my-branch
36
67
  or my-branch:::).
37
68
 
38
- CMD Command to launch in the worktree (default: \$WTCODE_CMD,
39
- or first available of: ${WTCODE_CMDS_TO_TRY[*]}, \$SHELL).
69
+ CMD Command to launch in the worktree (default: \$WTCODE_TOOL,
70
+ or first available of: ${WTCODE_TOOLS_TO_TRY[*]}, \$SHELL).
40
71
 
41
72
  --exec Skip branch argument; select interactively, then launch CMD.
42
73
 
74
+ --base REF Fork a brand-new branch from REF (same as \$WTCODE_BASE_REF).
75
+
76
+ Interactive picker keys (when BRANCH is omitted):
77
+ ctrl-s scope: all, worktree, local, remote (all and worktree also show and
78
+ match a worktree directory that drifted from its branch name
79
+ or sits outside the main checkout and worktrees root)
80
+ ctrl-o order: recent, name (remote shown as a suffix), remote/name
81
+ ctrl-r base for a new branch: the default, the current branch (or
82
+ origin/HEAD, when that differs from the default)
83
+ ctrl-t tool: nested picker of installed tools and recent commands;
84
+ type a new command/args to launch it (unless CMD was given)
85
+ alt-enter create a new branch named after what you typed (never the
86
+ highlighted one); plain Enter does that too when nothing matches
87
+ ctrl-l git remote update, then reload
88
+ Changes made in the picker are remembered for next time, even if canceled.
89
+
43
90
  Environment variables:
44
- WTCODE_CMD Default tool to launch (e.g., claude, lazygit, vim)
91
+ WTCODE_TOOL Default tool to launch (e.g., claude, lazygit, vim); WTCODE_CMD is a deprecated alias
92
+ WTCODE_USE_CURRENT_BRANCH
93
+ When creating a brand-new branch, fork it from the
94
+ current HEAD instead of the default: origin/HEAD
95
+ (falling back to origin/main, then origin/master)
96
+ WTCODE_BASE_REF Fork brand-new branches from this ref (overrides the above)
97
+ WTCODE_BRANCH_SCOPE Initial picker scope (all|worktree|local|remote)
98
+ WTCODE_BRANCH_ORDER Initial picker order (recent|name|full)
99
+ WTCODE_STATE_FILE Where the picker remembers its last state
100
+ (default: \$XDG_STATE_HOME/wtcode/state; empty disables)
45
101
  WTCODE_TMUX_MODE When in tmux, control how the tool is launched:
46
- send-keys - send command to current pane (default in tmux)
102
+ send-keys - send command to current pane (default in tmux,
103
+ unless run by a known AI coding agent -- see below)
47
104
  split-window - create split pane with send-keys
48
105
  new-window - create new window with send-keys
49
106
  exec - exec the tool directly (opt out of send-keys)
50
107
  WTCODE_TERMINAL_MODE Outside tmux, control how the tool is launched:
51
108
  send-keys - reuse the current tab/window via AppleScript
52
- (default; macOS only). Terminal.app and Ghostty
53
- 1.3+ use their own scripting support; any other
54
- terminal falls back to simulated keystrokes
109
+ (default; macOS only, unless run by a known AI
110
+ coding agent -- see below). Terminal.app and
111
+ Ghostty 1.3+ use their own scripting support;
112
+ any other terminal falls back to simulated
113
+ keystrokes
55
114
  exec - exec the tool directly (opt out of send-keys)
115
+
116
+ When wtcode is invoked by a known AI coding agent (detected via
117
+ CLAUDECODE, CURSOR_AGENT, GEMINI_CLI, CODEX_SANDBOX, or
118
+ CODEX_SANDBOX_NETWORK_DISABLED), both modes default to exec
119
+ instead of send-keys, since send-keys/AppleScript would inject
120
+ keystrokes into a pane/tab/window the agent doesn't control while
121
+ it's still working. Set the env var explicitly to override.
56
122
  WTCODE_DEBUG Enable debug tracing when set
57
123
  GIT_WORKTREE_ROOT Override the directory where worktrees are created
58
124
 
@@ -62,18 +128,467 @@ Examples:
62
128
  wtcode feature-x claude --resume # launch claude with --resume
63
129
  wtcode --exec claude --resume # select interactively, launch claude --resume
64
130
  wtcode :new-feature # create new branch and worktree
65
- WTCODE_CMD=cursor wtcode feature # use cursor as the default tool
131
+ WTCODE_TOOL=cursor wtcode feature # use cursor as the default tool
66
132
  USAGE
67
133
  }
68
134
 
69
135
  # commands to try as default, in order of preference
70
- WTCODE_CMDS_TO_TRY=(
71
- ${WTCODE_CMD:-}
136
+ WTCODE_TOOLS_TO_TRY=(
137
+ $WTCODE_TOOL
72
138
  claude
73
139
  aider
74
140
  codex
75
141
  )
76
142
 
143
+ ###############################################################################
144
+ ## --default-base-ref -- ref to fork a brand-new branch from
145
+ ###############################################################################
146
+ # defaults to origin/HEAD (falling back to origin/main, then origin/master),
147
+ # so new worktrees start from the repo's default branch instead of whatever
148
+ # happens to be checked out where wtcode was invoked. set
149
+ # WTCODE_USE_CURRENT_BRANCH=true to restore the old behavior of forking from
150
+ # the current HEAD. only consults local remote-tracking refs -- never
151
+ # touches the network.
152
+ --default-base-ref() {
153
+ if [[ -n $WTCODE_BASE_REF ]]; then
154
+ echo "$WTCODE_BASE_REF"
155
+ return
156
+ fi
157
+ if [[ $WTCODE_USE_CURRENT_BRANCH == true ]]; then
158
+ git rev-parse HEAD
159
+ return
160
+ fi
161
+ local ref
162
+ for ref in origin/HEAD origin/main origin/master; do
163
+ git rev-parse --verify -q "$ref" &>/dev/null && { echo "$ref"; return; }
164
+ done
165
+ git rev-parse HEAD
166
+ }
167
+
168
+ ###############################################################################
169
+ ## interactive picker support -- remembered state, key-binding callbacks
170
+ ###############################################################################
171
+ # The fzf picker lets you cycle some options with key bindings:
172
+ # ctrl-s scope ctrl-o order ctrl-r base ref ctrl-l update
173
+ # alt-enter create a new branch named after what you typed, not the highlighted one
174
+ # ctrl-t tool: opens a nested picker of installed tools and recently
175
+ # launched commands; type anything to launch a new command/args
176
+ # The current values live in small files under a temp dir ($1 of the --fzf-*
177
+ # callbacks, which fzf runs as separate processes); every change is also saved
178
+ # to $WTCODE_STATE_FILE right away, for next time. none of this is touched when
179
+ # a BRANCH is given on the command line.
180
+
181
+ WTCODE_SCOPES=(all worktree local remote)
182
+ WTCODE_ORDERS=(recent name full) # full: sort by remote/name, name: by name alone
183
+
184
+ --self() {
185
+ local src=${BASH_SOURCE[0]}
186
+ echo "$(cd "$(dirname "$src")" && pwd)/$(basename "$src")"
187
+ }
188
+
189
+ # --state-get KEY -- last remembered value (empty if none)
190
+ --state-get() {
191
+ [[ -n $WTCODE_STATE_FILE && -r $WTCODE_STATE_FILE ]] || return 0
192
+ sed -n "s/^$1=//p" "$WTCODE_STATE_FILE" | tail -1
193
+ }
194
+
195
+ # --state-save KEY=VALUE... -- remember values, keeping the other keys
196
+ --state-save() {
197
+ [[ -n $WTCODE_STATE_FILE ]] || return 0
198
+ {
199
+ mkdir -p "$(dirname "$WTCODE_STATE_FILE")"
200
+ local tmp=$WTCODE_STATE_FILE.$$ kv
201
+ { [[ -r $WTCODE_STATE_FILE ]] && cat "$WTCODE_STATE_FILE"; true; } > "$tmp"
202
+ for kv; do
203
+ grep -v "^${kv%%=*}=" "$tmp" > "$tmp.2" || true
204
+ printf '%s\n' "$kv" >> "$tmp.2"
205
+ mv "$tmp.2" "$tmp"
206
+ done
207
+ mv "$tmp" "$WTCODE_STATE_FILE"
208
+ } 2>/dev/null || --msg "could not save state to $WTCODE_STATE_FILE"
209
+ }
210
+
211
+ # --pick-valid VALUE DEFAULT CHOICES... -- VALUE if among CHOICES, else DEFAULT
212
+ --pick-valid() {
213
+ local v=$1 d=$2 c; shift 2
214
+ for c; do [[ $c == "$v" ]] && { echo "$v"; return; }; done
215
+ echo "$d"
216
+ }
217
+
218
+ # --next-in-cycle CURRENT ITEMS... -- the item after CURRENT, wrapping around
219
+ --next-in-cycle() {
220
+ local cur=$1 item take=false; shift
221
+ for item; do
222
+ $take && { echo "$item"; return; }
223
+ [[ $item == "$cur" ]] && take=true
224
+ done
225
+ echo "$1"
226
+ }
227
+
228
+ # --base-ref-for MODE -- the ref a new branch is forked from in each base mode
229
+ --base-ref-for() {
230
+ case $1 in
231
+ current) echo HEAD ;;
232
+ origin) git rev-parse --verify -q origin/HEAD &>/dev/null && echo origin/HEAD || --default-base-ref ;;
233
+ *) --default-base-ref ;;
234
+ esac
235
+ }
236
+
237
+ # --base-modes -- the distinct bases ^r cycles: default, current branch, and
238
+ # origin/HEAD (only when that isn't what default already is)
239
+ --base-modes() {
240
+ local def head; def=$(--default-base-ref); head=$(git rev-parse HEAD 2>/dev/null) || head=
241
+ echo default
242
+ [[ $def == "$head" ]] || echo current # default fell back to HEAD: same thing
243
+ [[ $(--base-ref-for origin) == "$def" ]] || echo origin
244
+ }
245
+
246
+ # --base-label MODE -- "<exact branch> (why)", what a new branch would fork from
247
+ --base-label() {
248
+ local ref name
249
+ case $1 in
250
+ current) name=$(git branch --show-current 2>/dev/null); echo "${name:-HEAD} (current)" ;;
251
+ origin) echo "$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null || echo origin/HEAD) (origin/HEAD)" ;;
252
+ *)
253
+ ref=$(--default-base-ref)
254
+ if [[ -z $WTCODE_BASE_REF && $ref =~ ^[0-9a-f]{40}$ ]]; then name=$(git branch --show-current 2>/dev/null); name=${name:-HEAD} # (the fallback to HEAD)
255
+ elif [[ $ref == origin/HEAD ]]; then name=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null || echo "$ref")
256
+ else name=$ref
257
+ fi
258
+ echo "$name (default)" ;;
259
+ esac
260
+ }
261
+
262
+ # --split-command STRING -- shell-like words (quotes and backslashes), NUL-separated
263
+ --split-command() {
264
+ local s=$1 i=0 c q= word= started=false
265
+ while (( i < ${#s} )); do
266
+ c=${s:i:1}
267
+ if [[ $q == "'" ]]; then
268
+ if [[ $c == "'" ]]; then q=; else word+=$c; fi
269
+ elif [[ $q == '"' ]]; then
270
+ if [[ $c == '"' ]]; then q=
271
+ elif [[ $c == '\' && ${s:i+1:1} == ['"\$`'] ]]; then word+=${s:i+1:1}; i=$((i+1))
272
+ else word+=$c
273
+ fi
274
+ else
275
+ case $c in
276
+ "'"|'"') q=$c; started=true ;;
277
+ '\') word+=${s:i+1:1}; i=$((i+1)); started=true ;;
278
+ [[:space:]]) if $started; then printf '%s\0' "$word"; word=; started=false; fi ;;
279
+ *) word+=$c; started=true ;;
280
+ esac
281
+ fi
282
+ i=$((i+1))
283
+ done
284
+ if $started; then printf '%s\0' "$word"; fi
285
+ }
286
+
287
+ # --available-tools -- installed tools worth offering, one per line
288
+ --available-tools() {
289
+ local c seen=' '
290
+ for c in ${WTCODE_TOOL:+"$WTCODE_TOOL"} "${WTCODE_TOOLS_TO_TRY[@]}" "${SHELL:-bash}"; do
291
+ [[ -n $c && $seen != *" $c "* ]] || continue
292
+ # an explicit $WTCODE_TOOL is always offered; the rest only if installed
293
+ if [[ $c == "${WTCODE_TOOL-}" ]] || type "$c" &>/dev/null; then
294
+ echo "$c"; seen+="$c "
295
+ fi
296
+ done
297
+ }
298
+
299
+ # --fzf-init -- make the state dir for one picker session, print its path
300
+ --fzf-init() {
301
+ local dir v
302
+ dir=$(mktemp -d "${TMPDIR:-/tmp}/wtcode.XXXXXX")
303
+ # env vars/args win over what was remembered
304
+ v=${WTCODE_BRANCH_SCOPE:-$(--state-get scope)}
305
+ --pick-valid "$v" all "${WTCODE_SCOPES[@]}" > "$dir/scope"
306
+ v=${WTCODE_BRANCH_ORDER:-$(--state-get order)}
307
+ --pick-valid "$v" recent "${WTCODE_ORDERS[@]}" > "$dir/order"
308
+ if [[ -n $WTCODE_BASE_REF || $WTCODE_USE_CURRENT_BRANCH == true ]]; then
309
+ echo default > "$dir/base"
310
+ else
311
+ --pick-valid "$(--state-get base)" default $(--base-modes) > "$dir/base"
312
+ fi
313
+ --base-label "$(<"$dir/base")" > "$dir/base_label"
314
+ if [[ -n ${WTCODE_TOOL_FIXED-} ]]; then
315
+ echo "$WTCODE_TOOL_FIXED" > "$dir/tool_fixed"
316
+ else
317
+ # initial: $WTCODE_TOOL, else the last used command if still installed,
318
+ # else the first installed tool
319
+ v=${WTCODE_TOOL:-$(--state-get tool)}
320
+ [[ -n $WTCODE_TOOL ]] || type "${v%% *}" &>/dev/null || v=$(--available-tools | head -1)
321
+ printf '%s\n' "$v" > "$dir/tool"
322
+ fi
323
+ echo "$dir"
324
+ }
325
+
326
+ # --fzf-commit DIR -- apply the final picker state (changes were already saved
327
+ # as they were made, so they survive canceling too)
328
+ --fzf-commit() {
329
+ local dir=$1 base tool
330
+ base=$(<"$dir/base")
331
+ [[ $base == default ]] || WTCODE_PICKED_BASE=$(--base-ref-for "$base") # (not WTCODE_BASE_REF: that may be exported to the tool)
332
+ if [[ -r $dir/tool ]]; then
333
+ tool=$(<"$dir/tool")
334
+ WTCODE_PICKED_TOOL=$tool
335
+ --history-add "$tool"
336
+ fi
337
+ }
338
+
339
+ # --fzf-cycle DIR KIND -- advance scope|order|base|tool to its next value
340
+ --fzf-cycle() {
341
+ local dir=$1 kind=$2 choices
342
+ case $kind in
343
+ scope) choices=("${WTCODE_SCOPES[@]}") ;;
344
+ order) choices=("${WTCODE_ORDERS[@]}") ;;
345
+ base) choices=($(--base-modes)) ;;
346
+ esac
347
+ --next-in-cycle "$(<"$dir/$kind")" "${choices[@]}" > "$dir/$kind.new"
348
+ mv "$dir/$kind.new" "$dir/$kind"
349
+ # a scope/order reload cancels an update in flight, so its marker must go
350
+ [[ $kind == base ]] || rm -f "$dir/updating"
351
+ --state-save "$kind=$(<"$dir/$kind")" # remembered right away, even if the picker is canceled
352
+ [[ $kind == base ]] && --base-label "$(<"$dir/base")" > "$dir/base_label"
353
+ return 0
354
+ }
355
+
356
+ # recently launched commands, most recent first
357
+ --history-file() { [[ -n $WTCODE_STATE_FILE ]] && echo "$WTCODE_STATE_FILE.cmds" || true; }
358
+ --history-add() {
359
+ local f; f=$(--history-file); [[ -n $f && -n $1 ]] || return 0
360
+ { { printf '%s\n' "$1"; grep -vxF -- "$1" "$f" 2>/dev/null || true; } | head -20 > "$f.new" && mv "$f.new" "$f"; } 2>/dev/null || true
361
+ }
362
+
363
+ # --fzf-pick-tool DIR -- nested picker for the tool (run via ctrl-t)
364
+ # lists recent commands and installed tools; typing text that matches nothing
365
+ # (or pressing alt-enter) takes the typed text as-is, e.g. `claude --resume`
366
+ --fzf-pick-tool() {
367
+ local dir=$1 f out query key choice prompt='tool> '
368
+ [[ -e $dir/in_popup ]] || prompt='wtcode tool> '
369
+ f=$(--history-file)
370
+ out=$(
371
+ { [[ -n $f && -r $f ]] && cat "$f"; --available-tools; } | awk '!seen[$0]++' |
372
+ fzf --print-query --expect alt-enter --prompt "$prompt" --height 100% --layout reverse \
373
+ --header 'Pick a tool/recent command, or type a new command with args.
374
+ enter: pick highlighted (or typed text if nothing matches) alt-enter: use typed text as-is' ||
375
+ true
376
+ )
377
+ # fzf prints just the query when nothing matches, else query, key, choice
378
+ { read -r query || true; read -r key || true; read -r choice || true; } <<< "$out"
379
+ if [[ $key == alt-enter || -z $choice ]]; then choice=$query; fi
380
+ choice=${choice#"${choice%%[![:space:]]*}"} choice=${choice%"${choice##*[![:space:]]}"} # trim
381
+ if [[ -n $choice ]]; then
382
+ printf '%s\n' "$choice" > "$dir/tool"
383
+ # remembered right away: it's a deliberate choice that should survive
384
+ # canceling the main picker
385
+ --state-save tool="$choice"
386
+ --history-add "$choice"
387
+ fi
388
+ return 0
389
+ }
390
+
391
+ # --tmux-at-least MAJOR MINOR -- is the running tmux at least this version?
392
+ --tmux-at-least() {
393
+ local v; v=$(tmux -V 2>/dev/null) || return 1
394
+ [[ $v =~ ([0-9]+)\.([0-9]+) ]] || return 1
395
+ (( BASH_REMATCH[1] > $1 || (BASH_REMATCH[1] == $1 && BASH_REMATCH[2] >= $2) ))
396
+ }
397
+
398
+ # --fzf-pick DIR -- run the picker, print fzf's output (query, then selection).
399
+ # inside tmux, fzf's own --tmux is NOT used: since tmux 3.7/fzf 0.7x it opens
400
+ # a floating *pane*, whose keys go through tmux's key tables, so the prefix
401
+ # (e.g. OPTMUX's C-t) and root bindings (bind -n) get swallowed before fzf
402
+ # sees them. a display-popup overlay hands every key to the program instead.
403
+ --fzf-pick() {
404
+ local dir=$1
405
+ if [[ -n ${TMUX:-} ]] && type tmux &>/dev/null && --tmux-at-least 3 3; then
406
+ local self cmd envs=() v
407
+ self=$(--self)
408
+ # started marks that the popup really ran: if it was then dismissed (Esc on
409
+ # the popup, client detached, ...) that's a cancel, NOT a reason to fall
410
+ # back to showing a second, different picker
411
+ printf -v cmd ': > %q; %q %q --fzf-run %q > %q' "$dir/started" "$BASH" "$self" "$dir" "$dir/out"
412
+ : > "$dir/in_popup"
413
+ # a popup doesn't inherit our environment; pass along what matters
414
+ # (not exported, but the key-binding callbacks need them)
415
+ envs+=(-e "WTCODE_BASE_REF=$WTCODE_BASE_REF" -e "WTCODE_USE_CURRENT_BRANCH=$WTCODE_USE_CURRENT_BRANCH")
416
+ for v in $(compgen -e); do
417
+ [[ $v =~ ^(PATH|HOME|SHELL|USER|LOGNAME|TMPDIR|LANG|LC_[A-Z]+|XDG_[A-Z_]+|WTCODE_[A-Z_]+|GIT_[A-Z_]+|FZF_[A-Z_]+)$ ]] &&
418
+ envs+=(-e "$v=${!v}")
419
+ done
420
+ tmux display-popup -E ${TMUX_PANE:+-t "$TMUX_PANE"} -d "$PWD" -w 90% -h 70% -b rounded \
421
+ -T ' wtcode ' ${envs[@]+"${envs[@]}"} "$cmd" 2>/dev/null || true
422
+ if [[ -e $dir/started ]]; then
423
+ cat "$dir/out" 2>/dev/null || true # (empty when dismissed or canceled)
424
+ return 0
425
+ fi
426
+ rm -f "$dir/in_popup" # no client to show a popup on, etc.: fall back
427
+ fi
428
+ --fzf-run "$dir"
429
+ }
430
+
431
+ # --fzf-run DIR -- the picker itself (also what runs inside the tmux popup)
432
+ --fzf-run() {
433
+ local dir=$1 self b dir_q
434
+ { set +x; } &>/dev/null
435
+ self=$(--self)
436
+ printf -v b '%q %q' "$BASH" "$self" # command prefix for the key bindings
437
+ printf -v dir_q '%q' "$dir"
438
+ local opts=() prompt='branch> '
439
+ # a popup is titled ' wtcode '; anywhere else the prompt says what this is
440
+ [[ -e $dir/in_popup ]] || { opts+=(--tmux 90%,70%); prompt="wtcode $prompt"; }
441
+ if [[ ! -e $dir/tool_fixed ]]; then
442
+ opts+=(--bind "ctrl-t:execute($b --fzf-pick-tool $dir_q)+transform-header($b --fzf-header $dir_q)")
443
+ fi
444
+ --list-branches "$(<"$dir/scope")" "$(<"$dir/order")" |
445
+ WTCODE_BASE_REF=$WTCODE_BASE_REF WTCODE_USE_CURRENT_BRANCH=$WTCODE_USE_CURRENT_BRANCH \
446
+ fzf --ansi --color --print-query --delimiter=$'\t' --with-nth=2 --nth=1 \
447
+ --preview "$b --fzf-preview {1} {3}" \
448
+ --preview-window 'right,50%,wrap,<120(down,40%,wrap)' \
449
+ --header "$(--fzf-header "$dir")" \
450
+ --bind "ctrl-s:execute-silent($b --fzf-cycle $dir_q scope)+reload($b --fzf-list $dir_q)" \
451
+ --bind "ctrl-o:execute-silent($b --fzf-cycle $dir_q order)+reload($b --fzf-list $dir_q)" \
452
+ --bind "ctrl-r:execute-silent($b --fzf-cycle $dir_q base)+transform-header($b --fzf-header $dir_q)" \
453
+ --bind "ctrl-l:execute-silent(: > $dir_q/updating)+transform-header($b --fzf-header $dir_q)+reload($b --fzf-update $dir_q)" \
454
+ --bind "alt-enter:execute-silent(: > $dir_q/force_new)+accept" \
455
+ --bind "load:transform-header($b --fzf-header $dir_q)" \
456
+ ${opts[@]+"${opts[@]}"} \
457
+ --prompt "$prompt" || true
458
+ }
459
+
460
+ # --fzf-preview BRANCH [LINE] -- preview pane: the picker's one-line summary,
461
+ # then what BRANCH has relative to the base new branches fork from
462
+ # (git diff --stat BASE...BRANCH, plus the commits that are only on BRANCH)
463
+ --fzf-preview() {
464
+ local branch=${1-} line=${2-} base b=$'\e[1m' r=$'\e[0m'
465
+ [[ -z $line ]] || printf '%s\n\n' "$line"
466
+ [[ -n $branch ]] || return 0
467
+ base=$(--default-base-ref 2>/dev/null) || return 0
468
+ if [[ $branch == "$base" ]] || ! git rev-parse --verify -q "$branch^{commit}" &>/dev/null; then
469
+ return 0
470
+ fi
471
+ printf '%s%s...%s%s\n' "$b" "${base:0:40}" "$branch" "$r"
472
+ git log --oneline --no-decorate --color=never -n 10 "$base..$branch" 2>/dev/null
473
+ echo
474
+ git -c color.ui=always diff --stat=${FZF_PREVIEW_COLUMNS:-80} "$base...$branch" 2>/dev/null
475
+ }
476
+
477
+ --fzf-list() { --list-branches "$(<"$1/scope")" "$(<"$1/order")"; }
478
+
479
+ --fzf-update() {
480
+ GIT_TERMINAL_PROMPT=0 git remote update </dev/null &>/dev/null || true
481
+ rm -f "$1/updating" # (the header, in place, goes back on the next result)
482
+ --fzf-list "$1"
483
+ }
484
+
485
+ # --fzf-header DIR -- the current values are bold (fzf --ansi styles headers)
486
+ --fzf-header() {
487
+ local dir=$1 scope order tool update b=$'\e[1m' r=$'\e[0m'
488
+ case $(<"$dir/scope") in
489
+ worktree) scope=worktrees ;;
490
+ *) scope="$(<"$dir/scope") branches" ;; # all/local/remote branches
491
+ esac
492
+ case $(<"$dir/order") in full) order=remote/name ;; *) order=$(<"$dir/order") ;; esac
493
+ if [[ -r $dir/tool_fixed ]]; then tool="tool: $b$(<"$dir/tool_fixed")$r (given)"; else tool="^t tool: $b$(<"$dir/tool")$r"; fi
494
+ update='^l update remotes'
495
+ [[ ! -e $dir/updating ]] || update="^l ${b}updating remotes...$r" # inline: the rest of the header stays
496
+ echo "Enter: select, or create if nothing matches $tool"
497
+ echo "alt-enter: create new worktree ^r branch from: $b$(<"$dir/base_label")$r"
498
+ echo "^s scope: $b$scope$r ^o order: $b$order$r $update"
499
+ }
500
+
501
+ # --list-branches [SCOPE] [ORDER] -- candidates for the picker
502
+ # format: branch_name<TAB>indicator branch_name<TAB>date hash [upstream] subject
503
+ --list-branches() {
504
+ local scope=${1:-all} order=${2:-recent}
505
+ { set +x; } &>/dev/null
506
+ # NOTE: git branch --format uses ref-filter syntax which doesn't support
507
+ # pretty-format's %<(N,trunc) truncation. Using bash to handle truncation,
508
+ # alignment, and coloring instead.
509
+ local c_reset=$'\e[0m' c_green=$'\e[32m' c_cyan=$'\e[36m' c_blue=$'\e[34m' c_yellow=$'\e[33m' c_muted=$'\e[2;90m' # dim gray: the remote/ part and worktree paths
510
+ local c_red=$'\e[31m' local_branches sort_args
511
+ local_branches=$(git branch --format='%(refname:lstrip=2)')
512
+ # the two places worktrees are expected: the main checkout, and the root
513
+ # where wtcode puts the rest (see --enter-git-worktree)
514
+ local main_wt wt_root=${GIT_WORKTREE_ROOT:-}
515
+ main_wt=$(cd "$(git rev-parse --git-common-dir)/.." 2>/dev/null && pwd -P) || main_wt=
516
+ [[ -z $wt_root && -z $main_wt ]] || : ${wt_root:=$main_wt.worktrees}
517
+ # compare like with like: git reports canonical paths (no symlinks, no trailing /)
518
+ [[ ! -d $wt_root ]] || wt_root=$(cd "$wt_root" && pwd -P)
519
+ case $order in
520
+ name) sort_args=(-k3,3f -k1,1n) ;; # by name alone; local before remote on ties
521
+ full) sort_args=(-k4,4f) ;; # by remote/name
522
+ *) sort_args=(-k1,1n -k2,2nr) ;; # local first, then remote, each by recency
523
+ esac
524
+ {
525
+ # stage 1: isremote epoch namekey, then the x-prefixed fields (x keeps
526
+ # read from collapsing empty tab-separated ones)
527
+ if [[ $scope == all || $scope == local || $scope == worktree ]]; then
528
+ git branch --format=$'%(committerdate:unix)\tx%(refname:lstrip=2)\tx%(HEAD)\tx%(worktreepath)\tx%(committerdate:relative)\tx%(objectname:short)\tx%(upstream:track)\tx%(contents:subject)' |
529
+ while IFS=$'\t' read -r epoch branch head worktree rest; do
530
+ [[ $scope == worktree && $head != 'x*' && -z ${worktree#x} ]] && continue
531
+ printf '0\t%s\t%s\t%s\t%s\t%s\t%s\n' "$epoch" "$branch" "$branch" "$head" "$worktree" "$rest" || break
532
+ done
533
+ fi
534
+ # remote-tracking branches that don't have a local counterpart
535
+ if [[ $scope == all || $scope == remote ]]; then
536
+ git branch -r --format=$'%(committerdate:unix)\tx%(refname:lstrip=2)\tx>\tx\tx%(committerdate:relative)\tx%(objectname:short)\tx\tx%(contents:subject)' |
537
+ while IFS=$'\t' read -r epoch remote_ref head worktree rest; do
538
+ remote_ref=${remote_ref#x}
539
+ [[ $remote_ref == */HEAD ]] && continue
540
+ grep -qxF "${remote_ref#*/}" <<< "$local_branches" && continue
541
+ printf '1\t%s\tx%s\tx%s\t%s\t%s\t%s\n' "$epoch" "${remote_ref#*/}" "$remote_ref" "$head" "$worktree" "$rest" || break
542
+ done
543
+ fi
544
+ } |
545
+ sort -t $'\t' "${sort_args[@]}" |
546
+ cut -f4- |
547
+ while IFS=$'\t' read -r branch head worktree date hash upstream subject; do
548
+ # strip the x prefix from all fields
549
+ branch=${branch#x} head=${head#x} worktree=${worktree#x} date=${date#x} hash=${hash#x} upstream=${upstream#x} subject=${subject#x}
550
+ # determine indicator and color: HEAD (*) takes precedence, then worktree (+)
551
+ local pre= suf= nm=$branch
552
+ if [[ $head == '*' ]]; then ind='*'; c=$c_green
553
+ elif [[ -n $worktree ]]; then ind='+'; c=$c_cyan
554
+ elif [[ $head == '>' ]]; then
555
+ # remote-only: mute the remote part, as a prefix (remote/name) or,
556
+ # when sorting by name alone, as a suffix: name (remote)
557
+ ind=' '; c=$c_reset
558
+ if [[ $order == name ]]; then nm=${branch#*/} suf=" (${branch%%/*})"; else pre=${branch%%/*}/ nm=${branch#*/}; fi
559
+ else ind=' '; c=$c_reset
560
+ fi
561
+ # worktrees also show where they are, searchable too, when that is
562
+ # unexpected: under the root but drifted from the branch name (wtcode puts
563
+ # branch X at <root>/X), or somewhere else entirely. the main checkout and
564
+ # in-place worktrees show nothing. local/remote scopes are about branches
565
+ if [[ -n $worktree && $worktree != "$main_wt" && ( $scope == all || $scope == worktree ) ]]; then
566
+ local rel=$worktree
567
+ if [[ -n $wt_root && $worktree == "$wt_root"/* ]]; then
568
+ rel=${worktree#"$wt_root"/}
569
+ elif [[ $worktree == "$HOME"/* ]]; then
570
+ rel="~/${worktree#"$HOME"/}"
571
+ fi
572
+ (( ${#rel} > 28 )) && rel="..${rel: -26}"
573
+ [[ $rel == "$branch" ]] || suf=" @ $rel"
574
+ fi
575
+ # truncate the name to fit, then pad (before adding colors)
576
+ local room=$(( 50 - ${#pre} - ${#suf} )) padw
577
+ (( room < 4 )) && room=4
578
+ (( ${#nm} > room )) && nm="${nm:0:room-2}.."
579
+ padw=$(( 50 - ${#pre} - ${#nm} - ${#suf} )); (( padw < 0 )) && padw=0
580
+ printf -v pad '%*s' "$padw" ''
581
+ printf -v date '%14s' "$date"
582
+ # build display fields with colors embedded
583
+ name_shown="${c_muted}${pre}${c_reset}${c}${nm}${c_reset}${c_muted}${suf}${c_reset}${pad}"
584
+ date_shown="${c_blue}${date}${c_reset}"
585
+ hash_shown="${c_yellow}${hash}${c_reset}"
586
+ if [[ -n $upstream ]]; then upstream_shown="${c_red}${upstream}${c_reset} "; else upstream_shown=''; fi
587
+ printf '%s\t%s %s\t%s %s %s%s\n' \
588
+ "$branch" "$ind" "$name_shown" "$date_shown" "$hash_shown" "$upstream_shown" "$subject" || break
589
+ done
590
+ }
591
+
77
592
  ###############################################################################
78
593
  ## --enter-git-worktree -- select branch and create/switch worktree
79
594
  ###############################################################################
@@ -85,56 +600,22 @@ WTCODE_CMDS_TO_TRY=(
85
600
  if [[ $# -gt 0 ]]; then
86
601
  branch_name=$1; shift
87
602
  elif type fzf &>/dev/null; then
88
- # if branch name unspecified, use fzf to select one or enter a new name
89
- # format: branch_name<TAB>indicator branch_name date hash [upstream] subject
90
- branch_name=$(
91
- { set +x; } &>/dev/null
92
- # NOTE: git branch --format uses ref-filter syntax which doesn't support
93
- # pretty-format's %<(N,trunc) truncation. Using bash to handle truncation,
94
- # alignment, and coloring instead.
95
- c_reset=$'\e[0m' c_green=$'\e[32m' c_cyan=$'\e[36m' c_blue=$'\e[34m' c_yellow=$'\e[33m' c_red=$'\e[31m' c_dim=$'\e[2m'
96
- {
97
- git branch --sort=-committerdate \
98
- --format=$'x%(refname:lstrip=2)\tx%(HEAD)\tx%(worktreepath)\tx%(committerdate:relative)\tx%(objectname:short)\tx%(upstream:track)\tx%(contents:subject)'
99
- # also include remote-tracking branches that don't have a local counterpart
100
- local_branches=$(git branch --format='%(refname:lstrip=2)')
101
- git branch -r --sort=-committerdate \
102
- --format=$'x%(refname:lstrip=2)\tx>\tx\tx%(committerdate:relative)\tx%(objectname:short)\tx\tx%(contents:subject)' |
103
- while IFS=$'\t' read -r remote_ref rest; do
104
- remote_ref=${remote_ref#x}
105
- [[ $remote_ref == */HEAD ]] && continue
106
- local_name=${remote_ref#*/}
107
- grep -qxF "$local_name" <<< "$local_branches" && continue
108
- printf 'x%s\t%s\n' "$remote_ref" "$rest"
109
- done
110
- } |
111
- while IFS=$'\t' read -r branch head worktree date hash upstream subject; do
112
- # strip the x prefix from all fields
113
- branch=${branch#x} head=${head#x} worktree=${worktree#x} date=${date#x} hash=${hash#x} upstream=${upstream#x} subject=${subject#x}
114
- # determine indicator and color: HEAD (*) takes precedence, then worktree (+)
115
- if [[ $head == '*' ]]; then ind='*'; c=$c_green
116
- elif [[ -n $worktree ]]; then ind='+'; c=$c_cyan
117
- elif [[ $head == '>' ]]; then ind=' '; c=$c_dim
118
- else ind=' '; c=$c_reset
119
- fi
120
- # truncate then pad branch name, right-pad date (before adding colors)
121
- if (( ${#branch} > 50 )); then name="${branch:0:48}.."; else name=$branch; fi
122
- printf -v name '%-50s' "$name"
123
- printf -v date '%14s' "$date"
124
- # build display fields with colors embedded
125
- name_shown="${c}${name}${c_reset}"
126
- date_shown="${c_blue}${date}${c_reset}"
127
- hash_shown="${c_yellow}${hash}${c_reset}"
128
- if [[ -n $upstream ]]; then upstream_shown="${c_red}${upstream}${c_reset} "; else upstream_shown=''; fi
129
- printf '%s\t%s %s\t%s %s %s%s\n' \
130
- "$branch" "$ind" "$name_shown" "$date_shown" "$hash_shown" "$upstream_shown" "$subject"
131
- done |
132
- fzf --ansi --color --tmux --print-query --delimiter=$'\t' --with-nth=2 --nth=1 \
133
- --preview 'echo {3}' --preview-window 'down:2:wrap' \
134
- --prompt 'wtcode: select worktree/branch (prefix : to create new) > ' |
135
- cut -f1 |
136
- tail -1
137
- )
603
+ # if branch name unspecified, use fzf to select one or enter a new name.
604
+ # key bindings cycle scope/order/base/tool, see --fzf-* below
605
+ local fzf_dir
606
+ fzf_dir=$(--fzf-init)
607
+ # fzf prints the typed query, then the selection (if any)
608
+ local picked
609
+ picked=$(--fzf-pick "$fzf_dir")
610
+ if [[ -e $fzf_dir/force_new ]]; then
611
+ # alt-enter: the typed text is a new name, never an existing ref
612
+ branch_name=$(head -1 <<< "$picked")
613
+ [[ -n $branch_name ]] && branch_name=:$branch_name
614
+ else
615
+ branch_name=$(cut -f1 <<< "$picked" | tail -1)
616
+ fi
617
+ [[ -z ${branch_name-} ]] || --fzf-commit "$fzf_dir"
618
+ rm -rf "$fzf_dir"
138
619
  else # abort if fzf not available
139
620
  branch_name=${1:?Need a worktree/branch name as first argument}; shift
140
621
  fi
@@ -230,9 +711,14 @@ WTCODE_CMDS_TO_TRY=(
230
711
  --msg "creating worktree for branch: $branch_name"
231
712
  git worktree add -B "$branch_name" "$worktree_path" "$(git rev-parse "$branch_name")"
232
713
  else
233
- # fork the current HEAD and create the new worktree
234
- --msg "creating worktree with new branch: $branch_name"
235
- git worktree add -b "$branch_name" "$worktree_path" "$(git rev-parse HEAD)"
714
+ # fork a base ref and create the new worktree: defaults to origin/HEAD
715
+ # (falling back to origin/main, origin/master, then the current HEAD);
716
+ # set WTCODE_USE_CURRENT_BRANCH=true to always fork from the current
717
+ # branch instead
718
+ local base_ref
719
+ base_ref=${WTCODE_PICKED_BASE:-$(--default-base-ref)}
720
+ --msg "creating worktree with new branch: $branch_name (from $base_ref)"
721
+ git worktree add -b "$branch_name" "$worktree_path" "$base_ref"
236
722
  fi
237
723
  cd "$worktree_path"
238
724
 
@@ -423,8 +909,14 @@ APPLESCRIPT
423
909
  ###############################################################################
424
910
  --launch-code-tool() {
425
911
  # resolve the command to launch if not specified
912
+ if [[ $# -eq 0 && -n ${WTCODE_PICKED_TOOL-} ]]; then
913
+ # chosen in the interactive picker; may carry args, e.g. `claude --resume`
914
+ local _w _words=()
915
+ while IFS= read -r -d '' _w; do _words+=("$_w"); done < <(--split-command "$WTCODE_PICKED_TOOL")
916
+ [[ ${#_words[@]} -eq 0 ]] || set -- "${_words[@]}"
917
+ fi
426
918
  if [[ $# -eq 0 ]]; then
427
- for _cmd in "${WTCODE_CMDS_TO_TRY[@]}"; do
919
+ for _cmd in "${WTCODE_TOOLS_TO_TRY[@]}"; do
428
920
  [[ -n "$_cmd" ]] && type "$_cmd" &>/dev/null && set -- "$_cmd" && break
429
921
  done
430
922
  # fall back to an interactive shell
@@ -446,7 +938,7 @@ APPLESCRIPT
446
938
  # when inside tmux, default to send-keys for command history access
447
939
  # set WTCODE_TMUX_MODE=exec to opt out and exec directly
448
940
  if [[ -n ${TMUX:-} ]] && type tmux &>/dev/null; then
449
- local mode=${WTCODE_TMUX_MODE:-send-keys}
941
+ local mode=$WTCODE_TMUX_MODE
450
942
 
451
943
  case $mode in
452
944
  send-keys|split-window|new-window)
@@ -485,7 +977,7 @@ APPLESCRIPT
485
977
  # outside tmux: reuse the current tab/window if the terminal app supports
486
978
  # it (see --terminal-send-keys). set WTCODE_TERMINAL_MODE=exec to opt out
487
979
  if [[ -z ${TMUX:-} ]]; then
488
- local mode=${WTCODE_TERMINAL_MODE:-send-keys}
980
+ local mode=$WTCODE_TERMINAL_MODE
489
981
  case $mode in
490
982
  send-keys)
491
983
  if --terminal-send-keys "$@"; then
@@ -513,6 +1005,7 @@ APPLESCRIPT
513
1005
  -h() { --help "$@"; }
514
1006
 
515
1007
  --exec() {
1008
+ [[ $# -gt 0 ]] && WTCODE_TOOL_FIXED=$1 # CMD given: the picker can't change it
516
1009
  --enter-git-worktree
517
1010
  --launch-code-tool "$@"
518
1011
  }
@@ -526,6 +1019,13 @@ APPLESCRIPT
526
1019
  --launch-code-tool "$@"
527
1020
  }
528
1021
 
1022
+ # global options (before BRANCH/CMD): --base REF
1023
+ while [[ ${1-} == --base || ${1-} == --base=* ]]; do
1024
+ if [[ $1 == --base=* ]]; then WTCODE_BASE_REF=${1#--base=}; shift
1025
+ else WTCODE_BASE_REF=${2:?--base needs a ref}; shift 2
1026
+ fi
1027
+ done
1028
+
529
1029
  # dispatch $1 as a function when it starts with -
530
1030
  [[ ${1-} == -* ]] || set -- -- "$@"
531
1031
  "$@"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wtcode
3
- Version: 0.4.0
3
+ Version: 0.7.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
@@ -60,7 +60,7 @@ wtcode --exec CMD [CMD-ARGS...]
60
60
  ```
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
- - **`CMD`** -- Command to launch in the worktree. Defaults to `$WTCODE_CMD`, or the first available of: `claude`, `aider`, `codex`, `$SHELL`.
63
+ - **`CMD`** -- Command to launch in the worktree. Defaults to `$WTCODE_TOOL`, or the first available of: `claude`, `aider`, `codex`, `$SHELL`.
64
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.
65
65
  - **`--exec`** -- Skip the branch argument; select interactively via fzf, then launch `CMD`.
66
66
  - **`--help`** / **`--version`** -- Show help or version info.
@@ -74,14 +74,19 @@ wtcode feature-x claude --resume # launch claude with flags
74
74
  wtcode --exec claude --resume # select interactively, launch claude --resume
75
75
  wtcode :new-feature # create new branch and worktree
76
76
  wtcode # interactive branch selection via fzf
77
- WTCODE_CMD=cursor wtcode feature # use cursor as default tool
77
+ WTCODE_TOOL=cursor wtcode feature # use cursor as default tool
78
78
  ```
79
79
 
80
80
  ## Environment variables
81
81
 
82
82
  | Variable | Description |
83
83
  |---|---|
84
- | `WTCODE_CMD` | Default tool to launch (e.g., `claude`, `lazygit`, `vim`, `cursor`) |
84
+ | `WTCODE_TOOL` | Default tool to launch (e.g., `claude`, `lazygit`, `vim`, `cursor`); `WTCODE_CMD` is a deprecated alias |
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`) |
86
+ | `WTCODE_BASE_REF` | Fork brand-new branches from this ref (also `wtcode --base REF`) |
87
+ | `WTCODE_BRANCH_SCOPE` | Initial picker scope: `all`, `worktree`, `local`, `remote` |
88
+ | `WTCODE_BRANCH_ORDER` | Initial picker order: `recent`, `name`, `full` (remote/name) |
89
+ | `WTCODE_STATE_FILE` | Where the picker remembers its last state (default `$XDG_STATE_HOME/wtcode/state`; empty disables) |
85
90
  | `WTCODE_DEBUG` | Enable debug tracing when set to any value |
86
91
  | `GIT_WORKTREE_ROOT` | Override the directory where worktrees are created |
87
92
 
@@ -0,0 +1,9 @@
1
+ wtcode/__init__.py,sha256=h7R8i7Zgn-_8dPxK2hYN7eTNgSYrnmVyvmaDSvGQF_c,89
2
+ wtcode/__main__.py,sha256=hOvSYANO-q_rf2yy-AQoEOr18KFRSOF8Ezosf8eeFmM,335
3
+ wtcode/wtcode.sh,sha256=fIGxitblECbt7Rjdj8IeIHv0-pgke7BCZCccIgN6iS4,44180
4
+ wtcode-0.7.0.dist-info/licenses/LICENSE,sha256=MV9gr1nAVj5R60EM2BQuBhBxCG5V9ulX6-Ep_-LcLqA,1067
5
+ wtcode-0.7.0.dist-info/METADATA,sha256=YTVpAMP3q1RN8Yi31XUKnLZDmjgPcDofX28D-N1tdzM,4756
6
+ wtcode-0.7.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
7
+ wtcode-0.7.0.dist-info/entry_points.txt,sha256=QkSPHU86FQa-ujQk07fF4sX4DBQgWzDtgusop4AA5bc,48
8
+ wtcode-0.7.0.dist-info/top_level.txt,sha256=oftTSYYjDMqt33wa0c9MC15KuQ3eyRyoTLXy3PdkVfE,7
9
+ wtcode-0.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (84.0.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,9 +0,0 @@
1
- wtcode/__init__.py,sha256=TqbbEAt77M6Nx-V2Hn5cdAYQHAuTIzcW1KhT-e_nzhw,89
2
- wtcode/__main__.py,sha256=hOvSYANO-q_rf2yy-AQoEOr18KFRSOF8Ezosf8eeFmM,335
3
- wtcode/wtcode.sh,sha256=4jJihpDcSMISByTOW7zo77xDk6bxstQWCEc3d6d-cX4,21098
4
- wtcode-0.4.0.dist-info/licenses/LICENSE,sha256=MV9gr1nAVj5R60EM2BQuBhBxCG5V9ulX6-Ep_-LcLqA,1067
5
- wtcode-0.4.0.dist-info/METADATA,sha256=tltadRB3o50NbV1Pwd0NhNLte-ezlBXRceRFSrAzs_I,4137
6
- wtcode-0.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
7
- wtcode-0.4.0.dist-info/entry_points.txt,sha256=QkSPHU86FQa-ujQk07fF4sX4DBQgWzDtgusop4AA5bc,48
8
- wtcode-0.4.0.dist-info/top_level.txt,sha256=oftTSYYjDMqt33wa0c9MC15KuQ3eyRyoTLXy3PdkVfE,7
9
- wtcode-0.4.0.dist-info/RECORD,,