Scripts
The shell-agnostic executables in ~/.local/bin — usage, behavior, and dependencies for each.
dot_local/bin/ holds standalone sh, bash, and python3 executables. The executable_ prefix is a chezmoi attribute that sets the executable bit and is dropped from the target name, so dot_local/bin/executable_pj becomes ~/.local/bin/pj. bash, zsh, and fish all put ~/.local/bin on PATH, so every shell runs the same implementation.
This page covers the general-purpose scripts. The desktop scripts in the same directory are documented with their compositor:
niri-launch,niri-launch-ai,niri-launch-browser,niri-launch-terminal,niri-launch-webapp,cp-appid: niriomarchy-*: Hyprland
Why some helpers are shell functions
A script runs as a child process, and a child cannot change its parent shell’s working directory or read the parent’s in-memory history. Helpers that need to do either are therefore defined as shell functions, ported three times: fish/functions/*.fish, bash/functions/*.bash, and zsh/functions/*.zsh.
| Helper | Why it must be a function |
|---|---|
yy (and fish’s y) |
cds into the directory yazi exited in |
c |
cds when given an argument |
cd |
Replaces the cd builtin with a zoxide fallback |
ghg, ghn, gho |
cd into a repo that the script picked or cloned |
clip-last |
Reads the shell’s own history ($history[1] in fish, fc in bash/zsh) |
For ghg, ghn, and gho, the work lives in a script that prints a directory on stdout (and everything else on stderr). The shell function captures that path and cds into it. ghg’s script is named ghg-clone because an unrelated cargo binary already uses the name ghg.
Several scripts below also exist as fish functions of the same name (ai, c2p, extract_agents, fab, gdc, git-summary, lc-elir, mdcp, mdp, launch_omp_livediff_overlay, gh-sonr-org-migrate). In fish the function runs instead of the script. Both versions behave the same. See fish functions.
Overview
| Script | Language | One line |
|---|---|---|
ai |
sh | Pick an AI CLI with gum and launch it |
atuin-pick |
python3 | atuin history picker for a kitty or tmux split |
c2p |
sh | code2prompt with an fzf-picked template |
extract_agents |
sh | Copy all AGENTS.md files into a directory tree |
fab |
sh | fzf picker for Fabric patterns |
gdc |
sh | Discard all git changes after confirmation |
gh-sonr-org-migrate |
bash | Consolidate *sonr* GitHub orgs into sonr-io |
ghg-clone |
sh | ghq get and print the clone path |
ghn |
sh | Create a private repo, clone it, print the path |
gho |
sh | Pick a ghq repo or Obsidian vault, print the path |
git-summary |
sh | Aggregate a repo’s AGENTS.md files with code2prompt |
gop |
sh | Open the current repo’s web page |
launch_omp_livediff_overlay |
sh | omp overlay plus a livediff split in kitty |
lc-elir |
sh | Run /leetcode-eli5 via omp on picked solutions |
mdcp |
sh | Fetch a URL as Markdown, copy it, preview it |
mdp |
sh | fzf Markdown picker with mdfried preview |
pj |
bash | fzf task runner across every project task source |
yazi-nvim-open |
sh | yazi opener that targets the host Neovim |
ai
ai
Shows gum choose --header 'AI CLI' with claude, opencode, codex, hermes, and fractal, then execs the choice. claude runs as claude --dangerously-skip-permissions; the others run as the bare binary name. Esc or Ctrl-C exits 0.
Needs: gum and the chosen CLI.
atuin-pick
atuin-pick
Opens atuin’s interactive search (atuin search -i --filter-mode directory), limited to history from the current directory. It then execs an interactive fish. If you picked a command, the new fish runs echo '❯ <cmd>'; <cmd> via -C (--init-command), so aliases resolve, a cd persists, and the prompt appears below the output. Esc starts a plain fish.
It is written for launch contexts where atuin’s shell init never ran. It resolves atuin from PATH or ~/.atuin/bin/atuin, and seeds ATUIN_SESSION from atuin uuid if unset.
Launched by: ctrl+shift+p in kitty. kitty.conf opens it in a horizontal split (launch_hs atuin-pick). Under the kitty-tmux profile, tmux.conf runs it with tmux split-window -v -l 25% in the current pane’s directory. See kitty and atuin.
Needs: python3, atuin, fish.
c2p
c2p
Lists *.hbs files under ~/.config/code2prompt with fd, lets you pick one with fzf, and execs code2prompt --template <file>.
Needs: fd, fzf, code2prompt.
extract_agents
extract_agents <output-dir>
Creates <output-dir> and copies every AGENTS.md below the current directory into it with cp --parents, keeping the relative paths.
Needs: fd, GNU cp (--parents).
fab
fab
some-command | fab
Reads ~/.config/fabric/pattern_explainations.json. fzf lists pattern names, the preview pane (right, 60%, wrapped) shows each description, and ctrl-/ toggles the preview. The picked pattern runs as fabric-ai --pattern <name>, falling back to fabric if fabric-ai is not installed. Piped stdin is buffered to a temp file and fed to the pattern; the temp file is removed on exit.
Needs: jq, fzf, fabric-ai or fabric, and the pattern JSON.
gdc
gdc
Asks gum confirm "Discard all uncommitted changes? This is irreversible.". On yes, runs git reset --hard HEAD and git clean -fd.
Needs: gum, git.
gh-sonr-org-migrate
gh-sonr-org-migrate
A bash port of fish/functions/gh-sonr-org-migrate.fish, with the same flow:
Preflight
gh, gum, and jq exist and that gh auth status succeeds.Discover
sonr, excluding the target sonr-io. You pick which to migrate with gum choose --no-limit.Transfer
gh api -X POST repos/<org>/<repo>/transfer -f new_owner=sonr-io. If the name already exists in sonr-io, gum input asks for a new name (default <org>-<repo>). An empty name skips the repo.Settle
gum spin), because GitHub transfers are asynchronous.Delete
gum confirm and then gh api -X DELETE orgs/<org>.Needs: gh authenticated as owner of every org involved, gum, jq.
ghg-clone
ghg-clone <repo>
Runs ghq get <repo>, with its output sent to stderr. It then works out the on-disk path: it strips the scheme, any user@, the scp-style colon, and a trailing .git, and prefixes github.com/ when the input has no host. It checks that $(ghq root)/<host>/<user>/<repo> exists, runs zoxide add on it if zoxide is installed, and prints the path.
Accepts https://…, ssh://…, git@host:user/repo, host/user/repo, or user/repo.
Used by: the ghg shell function, which cds into the path, clears the screen, and runs onefetch. The fish function gtm also uses it to clone migrated repos.
Needs: ghq (GHQ_ROOT=~/Code in fish), optional zoxide.
ghn
ghn <repo-name>
Creates a new private repo and clones it:
- prad.codes (Gitea): pick an org from
tea organizations list, thentea repos create --privateandghq get https://prad.codes/<org>/<name>. - github.com: pick the owner
prdlkorwltbs, thengh repo create <owner>/<name> --privateandghq get.
You pick the host with gum choose. The script prints the clone directory, and the ghn shell function cds into it.
Needs: gum, ghq, and tea or gh.
gho
gho
An fzf picker with two lists. tab switches between them:
repo::ghq listplus the chezmoi source tree, listed asgithub.com/prdlk/dotfilesand mapped back to~/.local/share/chezmoi.--tiebreak=endfavors matches at the end of the name.vault:: the Obsidian vaults~/Documents/Obsidian/Personal,Walletbase, andResearch.
It prints the chosen directory. fzf’s bind snippets run under SHELL=/bin/sh, so they behave the same whatever the login shell. The gho shell function cds into the result, clears the screen, and runs onefetch in git repos.
Launched by: ctrl-o in fish and zsh.
Needs: ghq, fzf, onefetch (for the wrapper).
git-summary
git-summary [code2prompt flags…]
From anywhere inside a git repo, runs code2prompt <repo-root> --include AGENTS.md --include "**/AGENTS.md" --template ~/.config/code2prompt/agents-md.hbs. Extra flags such as --output-file or --tokens are passed through.
Needs: git, code2prompt, and the agents-md.hbs template.
gop
gop # origin, else upstream, else first remote
gop upstream # a named remote
gop owner/repo # github.com/owner/repo
gop <url> # any https/ssh/scp remote URL
Opens a repo’s web page for any forge whose URL layout is https://<host>/<owner>/<repo>, such as github.com and prad.codes. It reduces any remote form to <host>/<owner>/<repo>: it drops credentials, drops the port on ssh:// and git:// remotes, keeps the port and plain http on http:// remotes, and strips .git. It then runs $BROWSER (default xdg-open) on the result.
Needs: git, a browser or xdg-open.
launch_omp_livediff_overlay
launch_omp_livediff_overlay
Inside kitty with remote control enabled, runs kitty @ launch --type=overlay-main --cwd=current omp, then opens livediff in a vsplit next to that window with --keep-focus. It relies on $KITTY_LISTEN_ON rather than a hard-coded socket, because kitty appends -PID to listen_on socket names. It exits with an error if the variable is unset.
Needs: kitty with allow_remote_control/listen_on (kitty), omp (AI agents), livediff.
lc-elir
lc-elir [root]
Finds numbered LeetCode solution files (^\d+\..+\.(js|ts)$, for example 1365.some-slug.js) under root (default .). fzf multi-selects them, showing the last three path segments with a bat preview of the first 40 lines. Each pick runs omp -p "/leetcode-eli5 <file>".
Needs: fd, fzf, bat, omp, and the /leetcode-eli5 omp command.
mdcp
mdcp <url>
Fetches the URL as Markdown with curl.md, copies it with wl-copy or pbcopy, then pastes it back into mdfried for a rendered preview.
Needs: curl.md, mdfried, and wl-clipboard or pbcopy.
mdp
mdp
Lists *.md files up to 3 directories deep with fd. fzf shows an mdfried preview (right, 60%, wrapped), and the pick opens in mdfried.
Needs: fd, fzf, mdfried.
pj
pj [extra args…]
A task runner that searches every task source in the project and lets you pick one with fzf.
Sources. Each is listed only when its runner is installed:
| Source | Found in | Runner |
|---|---|---|
| npm scripts | package.json |
package manager from packageManager, else from the lockfile (bun.lock[b], pnpm-lock.yaml, yarn.lock, package-lock.json, searched upward), else npm, then <pm> run |
| Make | GNUmakefile, makefile, Makefile |
make. Targets come from a text parse of rules and .PHONY; make -p is avoided because it would run $(shell …) |
| just | justfile, .justfile, Justfile, JUSTFILE |
just |
| mise | mise.toml, .mise.toml, mise/config.toml, .mise/config.toml, .config/mise/config.toml, or a mise-tasks/.mise/tasks/mise/tasks dir |
mise run. Hidden tasks are skipped, and tasks inherited from parent directories are filtered out |
| devbox | devbox.json shell.scripts |
devbox run |
| poe | pyproject.toml [tool.poe.tasks] |
poe, else uv run poe |
| pdm | [tool.pdm.scripts] (skips _* and post_*) |
pdm run |
| rye | [tool.rye.scripts] |
rye run |
| taskipy | [tool.taskipy.tasks] |
task, else uv run task |
| hatch | [tool.hatch.envs.<env>.scripts] as env:name |
hatch run |
| console scripts | [tool.poetry.scripts], [project.scripts] |
poetry run for Poetry projects, else uv run |
Scanning. Walks from $PWD up to the git toplevel, inclusive. Outside a repo it stops at the first directory that produced tasks.
Picker. Rows are tagged with the tool (npm, pnpm, bun, make, just, mise, poe, py, …), so typing a tool name filters to it. A directory column appears when more than one project contributed tasks. The header shows the nearest project root. The preview (top, 6 lines) shows the task body: the npm script string, the Make recipe, just --show, mise tasks info, the devbox script, or the pyproject entry.
Run. cds into the task’s directory and execs the runner. Extra arguments are forwarded, with -- inserted for npm run, mise, and devbox. Tasks named dev, start, serve, watch, or namespaced variants like dev:web are wrapped in openlogs (found on PATH, via mise which, or in mise’s node installs) for a live log tail. Without openlogs they run unwrapped, with a note.
Env. PJ_QUIET=1 suppresses the “no task sources found” error. The fish down binding uses it.
Launched by: ctrl-p in fish and zsh, and down on an empty fish prompt.
Needs: bash, fzf, jq, python3 3.11+ (for tomllib), plus whichever runners the project uses.
yazi-nvim-open
yazi-nvim-open [vsplit|split|tabedit] <file>…
yazi’s edit opener. When run inside a Neovim terminal ($NVIM set), each file’s absolute path goes to the host Neovim through nvim --server "$NVIM" --remote-expr 'v:lua.EditFromYazi(mode, path)' (defined in the nvim config’s yazi_session.lua). Outside Neovim it execs a normal nvim, and the split modes do nothing. Set YAZI_OPEN_TRACE=<file> to log each call.
Used by: yazi.toml (the edit opener) and keymap.toml (ctrl-v vsplit, ctrl-x split). See yazi.
Needs: nvim, realpath.