mirror of
https://github.com/prdlk/dotfiles.git
synced 2026-11-02 11:32:42 +00:00
shell: fish functions -> ~/.local/bin executables + bash/zsh ports; manage ~/.agents
- 16 cross-shell utilities converted to sh/bash chezmoi executables: ai c2p cp-appid extract_agents fab gdc gh-sonr-org-migrate ghg-clone ghn gho git-summary launch_omp_livediff_overlay lc-elir mdcp mdp pj (ghg-clone not named ghg: unrelated cargo binary owns that name) - cwd/history helpers stay per-shell: yy c cd clip-last + ghg/ghn/gho cd-wrappers in bash/functions/ and zsh/functions/, loaded by rc files - fish keeps only shell-state functions; converted ones now hit PATH - cp-appid ignore gating moved to .local/bin path - dot_agents/skills: 82 skills materialized from dotstate storage so dot_claude skill symlinks resolve on fresh bootstrap; dotstate profile/symlinks.json released .agents/skills .bashrc .zshrc
This commit is contained in:
1 parent
6776366ce6
commit
6406208b35
637 files changed
+100297
-497
No files matched your search
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
# Choose and launch an AI CLI via gum.
|
||||
choice=$(gum choose --header 'AI CLI' claude opencode codex hermes fractal) || exit 0
|
||||
[ -n "$choice" ] || exit 0
|
||||
|
||||
case "$choice" in
|
||||
claude) exec claude --dangerously-skip-permissions ;;
|
||||
# labels match the binary names, so just run it;
|
||||
# add a dedicated case here if another tool needs flags
|
||||
*) exec "$choice" ;;
|
||||
esac
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Code2Prompt with fuzzy-search template.
|
||||
tpl=$(fd --type f --extension hbs . "$HOME/.config/code2prompt" | fzf) || exit 0
|
||||
[ -n "$tpl" ] || exit 0
|
||||
exec code2prompt --template "$tpl"
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/sh
|
||||
# Fuzzy-pick a desktop app ID and copy to clipboard.
|
||||
existing_dirs=""
|
||||
for d in /usr/share/applications \
|
||||
/usr/local/share/applications \
|
||||
"$HOME/.local/share/applications" \
|
||||
/var/lib/flatpak/exports/share/applications \
|
||||
"$HOME/.local/share/flatpak/exports/share/applications" \
|
||||
/var/lib/snapd/desktop/applications; do
|
||||
[ -d "$d" ] && existing_dirs="$existing_dirs $d"
|
||||
done
|
||||
|
||||
if [ -z "$existing_dirs" ]; then
|
||||
echo "No application directories found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# word splitting of $existing_dirs is intended
|
||||
# shellcheck disable=SC2086
|
||||
chosen=$(
|
||||
fd --type f --extension desktop . $existing_dirs \
|
||||
| while IFS= read -r f; do
|
||||
app_id=$(basename "$f" .desktop)
|
||||
name=$(sed -n 's/^Name=//p' "$f" | head -n1)
|
||||
[ -z "$name" ] && name=$app_id
|
||||
printf '%s\t%s\n' "$name" "$app_id"
|
||||
done \
|
||||
| sort -u -t "$(printf '\t')" -k1 \
|
||||
| fzf \
|
||||
--delimiter='\t' \
|
||||
--with-nth=1,2 \
|
||||
--prompt="App ID › " \
|
||||
--height=50% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--info=inline
|
||||
)
|
||||
|
||||
[ -n "$chosen" ] || exit 0
|
||||
|
||||
app_id=$(printf '%s\n' "$chosen" | cut -f2)
|
||||
printf '%s' "$app_id" | wl-copy
|
||||
echo "Copied: $app_id"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Copy all AGENTS.md files to a target directory while preserving structure.
|
||||
out_dir=$1
|
||||
|
||||
if [ -z "$out_dir" ]; then
|
||||
echo "Error: Output directory required." >&2
|
||||
echo "Usage: extract_agents <absolute_or_relative_output_path>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
# Find and copy preserving relative paths
|
||||
fd -t f -g "AGENTS.md" -x cp --parents "{}" "$out_dir"
|
||||
|
||||
echo "Extraction complete. Check $out_dir."
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/bin/sh
|
||||
# Fuzzy find and execute a Fabric pattern.
|
||||
json_file=$HOME/.config/fabric/pattern_explainations.json
|
||||
|
||||
if [ ! -f "$json_file" ]; then
|
||||
printf '\033[31mError: %s not found.\033[0m\n' "$json_file" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# fabric is aliased to fabric-ai in the shells; resolve the real binary here
|
||||
if command -v fabric-ai >/dev/null 2>&1; then
|
||||
fabric_bin=fabric-ai
|
||||
else
|
||||
fabric_bin=fabric
|
||||
fi
|
||||
|
||||
# 1. Safely capture piped input (if any) to a temp file — preserves all
|
||||
# formatting and newlines, avoids variable escaping issues
|
||||
temp_input=$(mktemp)
|
||||
trap 'rm -f "$temp_input"' EXIT INT TERM
|
||||
|
||||
if [ ! -t 0 ]; then
|
||||
cat > "$temp_input"
|
||||
fi
|
||||
|
||||
# 2. Parse JSON to TSV -> fzf (name in list, description in preview)
|
||||
selection=$(jq -r '.[] | "\(.name)\t\(.description)"' "$json_file" \
|
||||
| fzf --delimiter '\t' \
|
||||
--with-nth 1 \
|
||||
--preview 'echo {2}' \
|
||||
--preview-window 'right:60%:wrap' \
|
||||
--prompt 'Pattern > ' \
|
||||
--info inline \
|
||||
--bind 'ctrl-/:toggle-preview')
|
||||
|
||||
# 3. Execute with or without the piped data
|
||||
if [ -n "$selection" ]; then
|
||||
pattern=$(printf '%s\n' "$selection" | cut -f1)
|
||||
if [ -s "$temp_input" ]; then
|
||||
"$fabric_bin" --pattern "$pattern" < "$temp_input"
|
||||
else
|
||||
"$fabric_bin" --pattern "$pattern"
|
||||
fi
|
||||
fi
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
# Discard all git changes with gum confirmation.
|
||||
if gum confirm "Discard all uncommitted changes? This is irreversible."; then
|
||||
git reset --hard HEAD
|
||||
git clean -fd
|
||||
echo "Changes wiped."
|
||||
else
|
||||
echo "Operation aborted."
|
||||
fi
|
||||
Executable
+163
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env bash
|
||||
# gh-sonr-org-migrate
|
||||
# Migrates all repos from orgs matching "sonr" into sonr-io,
|
||||
# prompting via gum for renames on collision, then deletes emptied orgs.
|
||||
#
|
||||
# Requires: gh (authenticated as org owner on all orgs), gum, jq
|
||||
|
||||
TARGET_ORG=sonr-io
|
||||
|
||||
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
||||
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
||||
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
|
||||
cyan() { printf '\033[36m%s\033[0m\n' "$*"; }
|
||||
bcyan() { printf '\033[1;36m%s\033[0m\n' "$*"; }
|
||||
bgreen() { printf '\033[1;32m%s\033[0m\n' "$*"; }
|
||||
|
||||
# ── Preflight ────────────────────────────────────────────────────────────────
|
||||
for bin in gh gum jq; do
|
||||
if ! command -v "$bin" >/dev/null 2>&1; then
|
||||
red "missing dependency: $bin"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if ! gh auth status >/dev/null 2>&1; then
|
||||
red "gh is not authenticated"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Discover source orgs ─────────────────────────────────────────────────────
|
||||
# All orgs the user belongs to whose login contains "sonr", excluding target.
|
||||
mapfile -t source_orgs < <(gh api user/orgs --paginate --jq '.[].login' \
|
||||
| grep 'sonr' | grep -vx "$TARGET_ORG")
|
||||
|
||||
if [ "${#source_orgs[@]}" -eq 0 ]; then
|
||||
echo "No source orgs found. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cyan "Source orgs:"
|
||||
printf ' %s\n' "${source_orgs[@]}"
|
||||
cyan "Target org:"
|
||||
echo " $TARGET_ORG"
|
||||
echo
|
||||
|
||||
# Let the user confirm / trim the list interactively
|
||||
mapfile -t selected_orgs < <(printf '%s\n' "${source_orgs[@]}" | gum choose --no-limit \
|
||||
--header "Select orgs to migrate into $TARGET_ORG (space to toggle, enter to confirm)")
|
||||
|
||||
if [ "${#selected_orgs[@]}" -eq 0 ]; then
|
||||
echo "Nothing selected. Exiting."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────────────────
|
||||
repo_exists_in_target() {
|
||||
gh api "repos/$TARGET_ORG/$1" --silent >/dev/null 2>&1
|
||||
}
|
||||
|
||||
transfer_repo() {
|
||||
# $1 = source org, $2 = repo name, $3 = (optional) new name
|
||||
local src=$1 repo=$2 new_name=${3:-}
|
||||
local args=(-f "new_owner=$TARGET_ORG")
|
||||
if [ -n "$new_name" ]; then
|
||||
args+=(-f "new_name=$new_name")
|
||||
fi
|
||||
gh api -X POST "repos/$src/$repo/transfer" "${args[@]}" --silent
|
||||
}
|
||||
|
||||
# ── Migration ────────────────────────────────────────────────────────────────
|
||||
failed_transfers=()
|
||||
|
||||
for org in "${selected_orgs[@]}"; do
|
||||
echo
|
||||
bcyan "── $org ──"
|
||||
|
||||
mapfile -t repos < <(gh api "orgs/$org/repos" --paginate --jq '.[].name')
|
||||
|
||||
if [ "${#repos[@]}" -eq 0 ]; then
|
||||
echo " (no repos)"
|
||||
continue
|
||||
fi
|
||||
|
||||
for repo in "${repos[@]}"; do
|
||||
new_name=""
|
||||
|
||||
if repo_exists_in_target "$repo"; then
|
||||
yellow " ⚠ $repo already exists in $TARGET_ORG"
|
||||
while true; do
|
||||
new_name=$(gum input \
|
||||
--header "New name for $org/$repo (exists in $TARGET_ORG)" \
|
||||
--placeholder "$org-$repo" \
|
||||
--value "$org-$repo")
|
||||
if [ -z "$new_name" ]; then
|
||||
echo " skipped $org/$repo"
|
||||
new_name=__SKIP__
|
||||
break
|
||||
fi
|
||||
if repo_exists_in_target "$new_name"; then
|
||||
yellow " $new_name also exists — pick another"
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
[ "$new_name" = __SKIP__ ] && continue
|
||||
fi
|
||||
|
||||
label="$org/$repo → $TARGET_ORG/${new_name:-$repo}"
|
||||
if transfer_repo "$org" "$repo" "$new_name"; then
|
||||
green " ✓ $label"
|
||||
else
|
||||
red " ✗ failed: $label"
|
||||
failed_transfers+=("$org/$repo")
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ "${#failed_transfers[@]}" -gt 0 ]; then
|
||||
echo
|
||||
red "Failed transfers (orgs containing these will NOT be deleted):"
|
||||
printf ' %s\n' "${failed_transfers[@]}"
|
||||
fi
|
||||
|
||||
# ── Wait for transfers to settle ─────────────────────────────────────────────
|
||||
# GitHub repo transfers are asynchronous; give them time to drain.
|
||||
echo
|
||||
gum spin --title "Waiting 30s for transfers to settle..." -- sleep 30
|
||||
|
||||
# ── Delete emptied orgs ──────────────────────────────────────────────────────
|
||||
echo
|
||||
for org in "${selected_orgs[@]}"; do
|
||||
# Skip orgs with failed transfers
|
||||
org_had_failure=0
|
||||
for f in ${failed_transfers[@]+"${failed_transfers[@]}"}; do
|
||||
case "$f" in
|
||||
"$org"/*) org_had_failure=1; break ;;
|
||||
esac
|
||||
done
|
||||
if [ "$org_had_failure" -eq 1 ]; then
|
||||
yellow "skipping delete: $org (had failures)"
|
||||
continue
|
||||
fi
|
||||
|
||||
remaining=$(gh api "orgs/$org/repos" --paginate --jq '. | length' | jq -s 'add // 0')
|
||||
|
||||
if [ "$remaining" -gt 0 ]; then
|
||||
yellow "skipping delete: $org still has $remaining repo(s) (transfer pending?)"
|
||||
continue
|
||||
fi
|
||||
|
||||
if gum confirm "Delete empty org $org?"; then
|
||||
if gh api -X DELETE "orgs/$org" --silent; then
|
||||
green "✓ deleted $org"
|
||||
else
|
||||
red "✗ could not delete $org (API delete requires org owner; may need manual deletion in Settings)"
|
||||
fi
|
||||
else
|
||||
echo "kept $org"
|
||||
fi
|
||||
done
|
||||
|
||||
echo
|
||||
bgreen "Done."
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
# ghq get + resolve the on-disk clone path; prints the repo directory.
|
||||
# Shell wrappers (fish/bash/zsh `ghg` functions) cd into the printed path —
|
||||
# a child process can't change the caller's cwd.
|
||||
repo=$1
|
||||
if [ -z "$repo" ]; then
|
||||
echo "usage: ghg-clone <repo>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ghq natively understands https://, ssh://, git@host:…, and host/user/repo.
|
||||
# Keep its output on stderr so stdout stays clean for the wrapper's cd.
|
||||
ghq get "$repo" >&2 || exit
|
||||
|
||||
# Reduce the input to ghq's on-disk shape: <host>/<user>/<repo> —
|
||||
# strip scheme (https://, ssh://, git://), user@ (git@github.com:…),
|
||||
# turn scp host:path into host/path, drop trailing .git.
|
||||
rel=$(printf '%s\n' "$repo" \
|
||||
| sed -E 's#^[[:alnum:]]+://##; s#^[^@/]+@##; s#:#/#; s#\.git$##')
|
||||
|
||||
# bare "user/repo" with no host → default to github.com
|
||||
case "$rel" in
|
||||
*.*/*) ;;
|
||||
*) rel=github.com/$rel ;;
|
||||
esac
|
||||
|
||||
dir=$(ghq root)/$rel
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "ghg: expected clone at $dir but it does not exist" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v zoxide >/dev/null 2>&1 && zoxide add "$dir"
|
||||
printf '%s\n' "$dir"
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
# Create a new private repo (prad.codes via tea, or github.com via gh),
|
||||
# clone it with ghq, and print the clone directory for the shell wrapper to cd.
|
||||
repo=$1
|
||||
if [ -z "$repo" ]; then
|
||||
echo "usage: ghn <repo-name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
host=$(gum choose "prad.codes" "github.com") || exit 1
|
||||
|
||||
if [ "$host" = prad.codes ]; then
|
||||
owner=$(tea organizations list -o simple | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | gum choose) || exit 1
|
||||
[ -n "$owner" ] || exit 1
|
||||
|
||||
tea repos create --name "$repo" --owner "$owner" --private >&2 || exit 1
|
||||
|
||||
ghq get "https://prad.codes/$owner/$repo" >&2 || exit 1
|
||||
printf '%s\n' "$(ghq root)/prad.codes/$owner/$repo"
|
||||
else
|
||||
owner=$(gum choose "prdlk" "wltbs") || exit 1
|
||||
[ -n "$owner" ] || exit 1
|
||||
|
||||
gh repo create "$owner/$repo" --private >&2 || exit 1
|
||||
|
||||
ghq get "github.com/$owner/$repo" >&2 || exit 1
|
||||
printf '%s\n' "$(ghq root)/github.com/$owner/$repo"
|
||||
fi
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
# Pick a ghq repo or an Obsidian vault with fzf (tab toggles the list);
|
||||
# prints the directory for the shell wrapper to cd into.
|
||||
vaults="$HOME/Documents/Obsidian/Personal $HOME/Documents/Obsidian/Walletbase $HOME/Documents/Obsidian/Research"
|
||||
|
||||
# fzf runs bind actions with $SHELL -c; pin it to sh so the transform
|
||||
# snippet below is interpreted consistently regardless of login shell.
|
||||
selection=$(ghq list | SHELL=/bin/sh fzf --prompt="repo: " --tiebreak=end \
|
||||
--bind "tab:transform:if [ \"\$FZF_PROMPT\" = 'repo: ' ]; then
|
||||
echo \"change-prompt(vault: )+reload(printf '%s\\n' $vaults)\"
|
||||
else
|
||||
echo 'change-prompt(repo: )+reload(ghq list)'
|
||||
fi") || exit 1
|
||||
[ -n "$selection" ] || exit 1
|
||||
|
||||
# Vault entries are absolute paths; ghq entries are host/user/repo
|
||||
case "$selection" in
|
||||
/*) printf '%s\n' "$selection" ;;
|
||||
*) printf '%s\n' "$(ghq root)/$selection" ;;
|
||||
esac
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
# Aggregate all AGENTS.md files in the current git repo via code2prompt.
|
||||
repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
|
||||
if [ -z "$repo_root" ]; then
|
||||
echo "git-summary: not inside a git repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
template=$HOME/.config/code2prompt/agents-md.hbs
|
||||
if [ ! -f "$template" ]; then
|
||||
echo "git-summary: template not found at $template" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pass through any extra flags (e.g. --output-file, --tokens)
|
||||
exec code2prompt "$repo_root" \
|
||||
--include "AGENTS.md" \
|
||||
--include "**/AGENTS.md" \
|
||||
--template "$template" \
|
||||
"$@"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Overlay omp on the current kitty window with a livediff split beside it.
|
||||
#
|
||||
# kitty appends -PID to config-file listen_on sockets, so a hardcoded
|
||||
# --to unix:@kitty-local never matches. kitty @ resolves the real
|
||||
# address from $KITTY_LISTEN_ON on its own.
|
||||
if [ -z "${KITTY_LISTEN_ON:-}" ]; then
|
||||
echo "launch_omp_livediff_overlay: no kitty remote-control socket (KITTY_LISTEN_ON unset)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# omp overlays the current window; capture its window id
|
||||
wid=$(kitty @ launch --type=overlay-main --cwd=current omp) || exit
|
||||
|
||||
# livediff side by side in the same tab; keep focus on omp
|
||||
exec kitty @ launch --match "id:$wid" --location=vsplit --keep-focus --cwd=current livediff
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# fzf-pick LeetCode solution files and run /leetcode-eli5 on each via omp.
|
||||
root=${1:-.}
|
||||
|
||||
# Match only numbered problem files: 1365.some-slug.js anywhere in the tree
|
||||
files=$(fd --type f '^\d+\..+\.(js|ts)$' "$root" \
|
||||
| fzf --multi \
|
||||
--prompt='eli5> ' \
|
||||
--delimiter=/ \
|
||||
--with-nth=-3.. \
|
||||
--preview 'bat --color=always --line-range=:40 {}' \
|
||||
--preview-window=right:60%)
|
||||
[ -n "$files" ] || exit 1
|
||||
|
||||
printf '%s\n' "$files" | while IFS= read -r f; do
|
||||
echo "── /leetcode-eli5 $f"
|
||||
omp -p "/leetcode-eli5 $f"
|
||||
done
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/bin/sh
|
||||
# Fetch a URL as markdown, copy to clipboard, preview with mdfried.
|
||||
url=$1
|
||||
if [ -z "$url" ]; then
|
||||
echo "usage: mdcp <url>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command -v wl-copy >/dev/null 2>&1; then
|
||||
copy=wl-copy paste=wl-paste
|
||||
elif command -v pbcopy >/dev/null 2>&1; then
|
||||
copy=pbcopy paste=pbpaste
|
||||
else
|
||||
echo "mdcp: no clipboard tool (wl-copy/pbcopy) found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl.md "$url" | "$copy"
|
||||
"$paste" | mdfried
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Interactive Markdown selector and previewer.
|
||||
file=$(fd --type f --extension md --max-depth 3 \
|
||||
| fzf --preview 'mdfried {}' --preview-window=right:60%:wrap) || exit 0
|
||||
[ -n "$file" ] || exit 0
|
||||
exec mdfried "$file"
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
# fzf package.json script runner with auto-detected package manager.
|
||||
|
||||
# ── Locate nearest package.json (walk up) ──────────────────────────
|
||||
dir=$PWD
|
||||
pkg=""
|
||||
while :; do
|
||||
if [ -f "$dir/package.json" ]; then
|
||||
pkg="$dir/package.json"
|
||||
break
|
||||
fi
|
||||
[ "$dir" = / ] && break
|
||||
dir=$(dirname "$dir")
|
||||
done
|
||||
|
||||
if [ -z "$pkg" ]; then
|
||||
echo "pj: no package.json found in this tree" >&2
|
||||
exit 1
|
||||
fi
|
||||
root=$(dirname "$pkg")
|
||||
|
||||
# ── Detect package manager ─────────────────────────────────────────
|
||||
# Priority: packageManager field (corepack) → lockfiles (walking up
|
||||
# past $root to catch monorepo-root locks) → npm fallback
|
||||
pm=$(jq -r '.packageManager // empty' "$pkg")
|
||||
pm=${pm%%@*}
|
||||
|
||||
if [ -z "$pm" ]; then
|
||||
d=$root
|
||||
while [ -z "$pm" ]; do
|
||||
if [ -f "$d/bun.lockb" ] || [ -f "$d/bun.lock" ]; then
|
||||
pm=bun
|
||||
elif [ -f "$d/pnpm-lock.yaml" ]; then
|
||||
pm=pnpm
|
||||
elif [ -f "$d/yarn.lock" ]; then
|
||||
pm=yarn
|
||||
elif [ -f "$d/package-lock.json" ]; then
|
||||
pm=npm
|
||||
fi
|
||||
[ "$d" = / ] && break
|
||||
d=$(dirname "$d")
|
||||
done
|
||||
[ -n "$pm" ] || pm=npm
|
||||
fi
|
||||
|
||||
# ── Pick a script ──────────────────────────────────────────────────
|
||||
script=$(jq -r '.scripts // {} | keys[]' "$pkg" | fzf \
|
||||
--prompt="$pm run ❯ " \
|
||||
--header="$root" \
|
||||
--height=40% --layout=reverse --border \
|
||||
--preview="jq -r --arg s {} '.scripts[\$s]' \"$pkg\"" \
|
||||
--preview-window=up:2:wrap)
|
||||
|
||||
[ -n "$script" ] || exit 0
|
||||
|
||||
# ── Run (npm needs `--` to forward extra args) ────────────────────
|
||||
extra=("$@")
|
||||
if [ "$pm" = npm ] && [ $# -gt 0 ]; then
|
||||
extra=(-- "$@")
|
||||
fi
|
||||
|
||||
# Wrap long-running server scripts in openlogs (live log tail for agents).
|
||||
# Matches dev/start/serve and namespaced variants like dev:web, start:api.
|
||||
wrapper=""
|
||||
if [[ $script =~ ^(dev|start|serve|watch)(:.*)?$ ]]; then
|
||||
# Resolve the binary: PATH → mise resolution → mise installs glob
|
||||
ol=$(command -v openlogs || true)
|
||||
if [ -z "$ol" ] && command -v mise >/dev/null 2>&1; then
|
||||
ol=$(mise which openlogs 2>/dev/null || true)
|
||||
fi
|
||||
if [ -z "$ol" ]; then
|
||||
for cand in "$HOME"/.local/share/mise/installs/node/*/bin/openlogs; do
|
||||
[ -x "$cand" ] && { ol=$cand; break; }
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -n "$ol" ] && [ -x "$ol" ]; then
|
||||
wrapper=$ol
|
||||
else
|
||||
echo "pj: openlogs not found, running unwrapped" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$wrapper" ]; then
|
||||
echo "→ $wrapper $pm run $script $*" >&2
|
||||
exec "$wrapper" "$pm" run "$script" "${extra[@]}"
|
||||
else
|
||||
echo "→ $pm run $script $*" >&2
|
||||
exec "$pm" run "$script" "${extra[@]}"
|
||||
fi
|
||||
Reference in new issue
Block a user