---
title: Scripts
description: 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`: [niri](/desktop/niri)
- `omarchy-*`: [Hyprland](/desktop/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](/shell/fish#functions).

## Overview

| Script | Language | One line |
| --- | --- | --- |
| [`ai`](#ai) | sh | Pick an AI CLI with gum and launch it |
| [`atuin-pick`](#atuin-pick) | python3 | atuin history picker for a kitty or tmux split |
| [`c2p`](#c2p) | sh | code2prompt with an fzf-picked template |
| [`extract_agents`](#extract_agents) | sh | Copy all `AGENTS.md` files into a directory tree |
| [`fab`](#fab) | sh | fzf picker for Fabric patterns |
| [`gdc`](#gdc) | sh | Discard all git changes after confirmation |
| [`gh-sonr-org-migrate`](#gh-sonr-org-migrate) | bash | Consolidate `*sonr*` GitHub orgs into `sonr-io` |
| [`ghg-clone`](#ghg-clone) | sh | `ghq get` and print the clone path |
| [`ghn`](#ghn) | sh | Create a private repo, clone it, print the path |
| [`gho`](#gho) | sh | Pick a ghq repo or Obsidian vault, print the path |
| [`git-summary`](#git-summary) | sh | Aggregate a repo's `AGENTS.md` files with code2prompt |
| [`gop`](#gop) | sh | Open the current repo's web page |
| [`launch_omp_livediff_overlay`](#launch_omp_livediff_overlay) | sh | omp overlay plus a livediff split in kitty |
| [`lc-elir`](#lc-elir) | sh | Run `/leetcode-eli5` via omp on picked solutions |
| [`mdcp`](#mdcp) | sh | Fetch a URL as Markdown, copy it, preview it |
| [`mdp`](#mdp) | sh | fzf Markdown picker with mdfried preview |
| [`pj`](#pj) | bash | fzf task runner across every project task source |
| [`yazi-nvim-open`](#yazi-nvim-open) | sh | yazi opener that targets the host Neovim |

## ai

```sh
ai
```

Shows `gum choose --header 'AI CLI'` with `claude`, `opencode`, `codex`, `hermes`, and `fractal`, then `exec`s 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

```sh
atuin-pick
```

Opens atuin's interactive search (`atuin search -i --filter-mode directory`), limited to history from the current directory. It then `exec`s 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:** <kbd>ctrl+shift+p</kbd> 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](/terminal/kitty) and [atuin](/shell/atuin).

**Needs:** `python3`, `atuin`, `fish`.

## c2p

```sh
c2p
```

Lists `*.hbs` files under `~/.config/code2prompt` with `fd`, lets you pick one with fzf, and `exec`s `code2prompt --template <file>`.

**Needs:** `fd`, `fzf`, `code2prompt`.

## extract_agents

```sh
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

```sh
fab
some-command | fab
```

Reads `~/.config/fabric/pattern_explainations.json`. fzf lists pattern names, the preview pane (right, 60%, wrapped) shows each description, and <kbd>ctrl-/</kbd> 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

```sh
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

```sh
gh-sonr-org-migrate
```

A bash port of `fish/functions/gh-sonr-org-migrate.fish`, with the same flow:

1. **Preflight**

    Checks that `gh`, `gum`, and `jq` exist and that `gh auth status` succeeds.

2. **Discover**

    Lists your GitHub orgs whose login contains `sonr`, excluding the target `sonr-io`. You pick which to migrate with `gum choose --no-limit`.

3. **Transfer**

    For each repo in each org, runs `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.

4. **Settle**

    Waits 30 seconds (`gum spin`), because GitHub transfers are asynchronous.

5. **Delete**

    For every selected org that had no failed transfers and now has zero repos, asks `gum confirm` and then `gh api -X DELETE orgs/<org>`.

**Needs:** `gh` authenticated as owner of every org involved, `gum`, `jq`.

:::warning
In fish, run this script by its full path (`~/.local/bin/gh-sonr-org-migrate`). The fish file of the same name is a script body with no function in it, so typing just the name runs the fish flow and then this script. See [fish](/shell/fish#functions).
:::

## ghg-clone

```sh
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

```sh
ghn <repo-name>
```

Creates a new **private** repo and clones it:

- **prad.codes** (Gitea): pick an org from `tea organizations list`, then `tea repos create --private` and `ghq get https://prad.codes/<org>/<name>`.
- **github.com**: pick the owner `prdlk` or `wltbs`, then `gh repo create <owner>/<name> --private` and `ghq 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

```sh
gho
```

An fzf picker with two lists. <kbd>tab</kbd> switches between them:

- `repo:`: `ghq list` plus the chezmoi source tree, listed as `github.com/prdlk/dotfiles` and mapped back to `~/.local/share/chezmoi`. `--tiebreak=end` favors matches at the end of the name.
- `vault:`: the Obsidian vaults `~/Documents/Obsidian/Personal`, `Walletbase`, and `Research`.

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:** <kbd>ctrl-o</kbd> in fish and zsh.

**Needs:** `ghq`, `fzf`, `onefetch` (for the wrapper).

## git-summary

```sh
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

```sh
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

```sh
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](/terminal/kitty)), `omp` ([AI agents](/ai)), `livediff`.

## lc-elir

```sh
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

```sh
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

```sh
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

```sh
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 `exec`s 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 <kbd>down</kbd> binding uses it.

**Launched by:** <kbd>ctrl-p</kbd> in fish and zsh, and <kbd>down</kbd> on an empty fish prompt.

**Needs:** `bash`, `fzf`, `jq`, `python3` 3.11+ (for `tomllib`), plus whichever runners the project uses.

## yazi-nvim-open

```sh
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 `exec`s 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` (<kbd>ctrl-v</kbd> vsplit, <kbd>ctrl-x</kbd> split). See [yazi](/tools/yazi).

**Needs:** `nvim`, `realpath`.
