wtcode 0.5.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.5.0"
3
+ __version__ = "0.7.0"
wtcode/wtcode.sh CHANGED
@@ -6,11 +6,32 @@
6
6
  set -eu
7
7
  shopt -s extglob
8
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
+
9
20
  # declare all WTCODE_* env vars and their defaults in one place
10
- : ${WTCODE_CMD:=}
21
+ : ${WTCODE_TOOL:=${WTCODE_CMD:-}} # WTCODE_CMD: deprecated alias
11
22
  : ${WTCODE_USE_CURRENT_BRANCH:=false}
12
- : ${WTCODE_TMUX_MODE:=send-keys}
13
- : ${WTCODE_TERMINAL_MODE:=send-keys}
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
14
35
  : ${WTCODE_DEBUG:=}
15
36
 
16
37
  ${WTCODE_DEBUG:+set -x}
@@ -25,16 +46,18 @@ ${WTCODE_DEBUG:+set -x}
25
46
  tr '[:upper:]' '[:lower:]' | # lowercase
26
47
  sed 's/[^a-z0-9/_-]/-/g' | # replace non-alnum to hyphens
27
48
  sed 's/--*/-/g' | # collapse consecutive hyphens
28
- 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
29
52
  }
30
53
 
31
- WTCODE_VERSION=0.5.0
54
+ WTCODE_VERSION=0.7.0
32
55
  --version() { echo "wtcode $WTCODE_VERSION"; }
33
56
  --help() {
34
57
  cat <<USAGE
35
58
  wtcode $WTCODE_VERSION -- launch a code tool in a git worktree
36
59
 
37
- Usage: wtcode [BRANCH] [CMD [CMD-ARGS...]]
60
+ Usage: wtcode [--base REF] [BRANCH] [CMD [CMD-ARGS...]]
38
61
  wtcode --exec CMD [CMD-ARGS...]
39
62
 
40
63
  BRANCH Git branch or worktree name to switch to.
@@ -43,28 +66,59 @@ Usage: wtcode [BRANCH] [CMD [CMD-ARGS...]]
43
66
  (use multiple colons to avoid fzf matching, e.g., :::my-branch
44
67
  or my-branch:::).
45
68
 
46
- CMD Command to launch in the worktree (default: \$WTCODE_CMD,
47
- 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).
48
71
 
49
72
  --exec Skip branch argument; select interactively, then launch CMD.
50
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
+
51
90
  Environment variables:
52
- 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
53
92
  WTCODE_USE_CURRENT_BRANCH
54
93
  When creating a brand-new branch, fork it from the
55
94
  current HEAD instead of the default: origin/HEAD
56
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)
57
101
  WTCODE_TMUX_MODE When in tmux, control how the tool is launched:
58
- 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)
59
104
  split-window - create split pane with send-keys
60
105
  new-window - create new window with send-keys
61
106
  exec - exec the tool directly (opt out of send-keys)
62
107
  WTCODE_TERMINAL_MODE Outside tmux, control how the tool is launched:
63
108
  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
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
67
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.
68
122
  WTCODE_DEBUG Enable debug tracing when set
69
123
  GIT_WORKTREE_ROOT Override the directory where worktrees are created
70
124
 
@@ -74,13 +128,13 @@ Examples:
74
128
  wtcode feature-x claude --resume # launch claude with --resume
75
129
  wtcode --exec claude --resume # select interactively, launch claude --resume
76
130
  wtcode :new-feature # create new branch and worktree
77
- WTCODE_CMD=cursor wtcode feature # use cursor as the default tool
131
+ WTCODE_TOOL=cursor wtcode feature # use cursor as the default tool
78
132
  USAGE
79
133
  }
80
134
 
81
135
  # commands to try as default, in order of preference
82
- WTCODE_CMDS_TO_TRY=(
83
- $WTCODE_CMD
136
+ WTCODE_TOOLS_TO_TRY=(
137
+ $WTCODE_TOOL
84
138
  claude
85
139
  aider
86
140
  codex
@@ -96,6 +150,10 @@ WTCODE_CMDS_TO_TRY=(
96
150
  # the current HEAD. only consults local remote-tracking refs -- never
97
151
  # touches the network.
98
152
  --default-base-ref() {
153
+ if [[ -n $WTCODE_BASE_REF ]]; then
154
+ echo "$WTCODE_BASE_REF"
155
+ return
156
+ fi
99
157
  if [[ $WTCODE_USE_CURRENT_BRANCH == true ]]; then
100
158
  git rev-parse HEAD
101
159
  return
@@ -107,6 +165,430 @@ WTCODE_CMDS_TO_TRY=(
107
165
  git rev-parse HEAD
108
166
  }
109
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
+
110
592
  ###############################################################################
111
593
  ## --enter-git-worktree -- select branch and create/switch worktree
112
594
  ###############################################################################
@@ -118,60 +600,22 @@ WTCODE_CMDS_TO_TRY=(
118
600
  if [[ $# -gt 0 ]]; then
119
601
  branch_name=$1; shift
120
602
  elif type fzf &>/dev/null; then
121
- # if branch name unspecified, use fzf to select one or enter a new name
122
- # format: branch_name<TAB>indicator branch_name date hash [upstream] subject
123
- branch_name=$(
124
- { set +x; } &>/dev/null
125
- # NOTE: git branch --format uses ref-filter syntax which doesn't support
126
- # pretty-format's %<(N,trunc) truncation. Using bash to handle truncation,
127
- # alignment, and coloring instead.
128
- 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'
129
- {
130
- git branch --sort=-committerdate \
131
- --format=$'x%(refname:lstrip=2)\tx%(HEAD)\tx%(worktreepath)\tx%(committerdate:relative)\tx%(objectname:short)\tx%(upstream:track)\tx%(contents:subject)'
132
- # also include remote-tracking branches that don't have a local counterpart
133
- local_branches=$(git branch --format='%(refname:lstrip=2)')
134
- git branch -r --sort=-committerdate \
135
- --format=$'x%(refname:lstrip=2)\tx>\tx\tx%(committerdate:relative)\tx%(objectname:short)\tx\tx%(contents:subject)' |
136
- while IFS=$'\t' read -r remote_ref rest; do
137
- remote_ref=${remote_ref#x}
138
- [[ $remote_ref == */HEAD ]] && continue
139
- local_name=${remote_ref#*/}
140
- grep -qxF "$local_name" <<< "$local_branches" && continue
141
- printf 'x%s\t%s\n' "$remote_ref" "$rest"
142
- done
143
- } |
144
- while IFS=$'\t' read -r branch head worktree date hash upstream subject; do
145
- # strip the x prefix from all fields
146
- branch=${branch#x} head=${head#x} worktree=${worktree#x} date=${date#x} hash=${hash#x} upstream=${upstream#x} subject=${subject#x}
147
- # determine indicator and color: HEAD (*) takes precedence, then worktree (+)
148
- if [[ $head == '*' ]]; then ind='*'; c=$c_green
149
- elif [[ -n $worktree ]]; then ind='+'; c=$c_cyan
150
- elif [[ $head == '>' ]]; then ind=' '; c=$c_dim
151
- else ind=' '; c=$c_reset
152
- fi
153
- # truncate then pad branch name, right-pad date (before adding colors)
154
- if (( ${#branch} > 50 )); then name="${branch:0:48}.."; else name=$branch; fi
155
- printf -v name '%-50s' "$name"
156
- printf -v date '%14s' "$date"
157
- # build display fields with colors embedded
158
- name_shown="${c}${name}${c_reset}"
159
- date_shown="${c_blue}${date}${c_reset}"
160
- hash_shown="${c_yellow}${hash}${c_reset}"
161
- if [[ -n $upstream ]]; then upstream_shown="${c_red}${upstream}${c_reset} "; else upstream_shown=''; fi
162
- printf '%s\t%s %s\t%s %s %s%s\n' \
163
- "$branch" "$ind" "$name_shown" "$date_shown" "$hash_shown" "$upstream_shown" "$subject"
164
- done |
165
- fzf --ansi --color --tmux 90%,70% --print-query --delimiter=$'\t' --with-nth=2 --nth=1 \
166
- --preview 'echo {3}' --preview-window 'down:2:wrap' \
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 '> ' |
172
- cut -f1 |
173
- tail -1
174
- )
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"
175
619
  else # abort if fzf not available
176
620
  branch_name=${1:?Need a worktree/branch name as first argument}; shift
177
621
  fi
@@ -272,7 +716,7 @@ WTCODE_CMDS_TO_TRY=(
272
716
  # set WTCODE_USE_CURRENT_BRANCH=true to always fork from the current
273
717
  # branch instead
274
718
  local base_ref
275
- base_ref=$(--default-base-ref)
719
+ base_ref=${WTCODE_PICKED_BASE:-$(--default-base-ref)}
276
720
  --msg "creating worktree with new branch: $branch_name (from $base_ref)"
277
721
  git worktree add -b "$branch_name" "$worktree_path" "$base_ref"
278
722
  fi
@@ -465,8 +909,14 @@ APPLESCRIPT
465
909
  ###############################################################################
466
910
  --launch-code-tool() {
467
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
468
918
  if [[ $# -eq 0 ]]; then
469
- for _cmd in "${WTCODE_CMDS_TO_TRY[@]}"; do
919
+ for _cmd in "${WTCODE_TOOLS_TO_TRY[@]}"; do
470
920
  [[ -n "$_cmd" ]] && type "$_cmd" &>/dev/null && set -- "$_cmd" && break
471
921
  done
472
922
  # fall back to an interactive shell
@@ -555,6 +1005,7 @@ APPLESCRIPT
555
1005
  -h() { --help "$@"; }
556
1006
 
557
1007
  --exec() {
1008
+ [[ $# -gt 0 ]] && WTCODE_TOOL_FIXED=$1 # CMD given: the picker can't change it
558
1009
  --enter-git-worktree
559
1010
  --launch-code-tool "$@"
560
1011
  }
@@ -568,6 +1019,13 @@ APPLESCRIPT
568
1019
  --launch-code-tool "$@"
569
1020
  }
570
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
+
571
1029
  # dispatch $1 as a function when it starts with -
572
1030
  [[ ${1-} == -* ]] || set -- -- "$@"
573
1031
  "$@"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wtcode
3
- Version: 0.5.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,15 +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
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) |
86
90
  | `WTCODE_DEBUG` | Enable debug tracing when set to any value |
87
91
  | `GIT_WORKTREE_ROOT` | Override the directory where worktrees are created |
88
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=nbWCIJu9WB6FsYqofznzsWb1GwPw1td8hQEnu2FAYJ4,89
2
- wtcode/__main__.py,sha256=hOvSYANO-q_rf2yy-AQoEOr18KFRSOF8Ezosf8eeFmM,335
3
- wtcode/wtcode.sh,sha256=tkm04c_aArDTAd3PWCBAvayWfsHe7227oUyafFqxNMo,22879
4
- wtcode-0.5.0.dist-info/licenses/LICENSE,sha256=MV9gr1nAVj5R60EM2BQuBhBxCG5V9ulX6-Ep_-LcLqA,1067
5
- wtcode-0.5.0.dist-info/METADATA,sha256=UPiHK_0pi1vWGZ22c1ker-cW70dq2hw3eelfsJxB-ec,4327
6
- wtcode-0.5.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
7
- wtcode-0.5.0.dist-info/entry_points.txt,sha256=QkSPHU86FQa-ujQk07fF4sX4DBQgWzDtgusop4AA5bc,48
8
- wtcode-0.5.0.dist-info/top_level.txt,sha256=oftTSYYjDMqt33wa0c9MC15KuQ3eyRyoTLXy3PdkVfE,7
9
- wtcode-0.5.0.dist-info/RECORD,,