From c32d147bbb3d5e93bb61eed2009a3cecd40feb85 Mon Sep 17 00:00:00 2001 From: kostakost2 <119595598+kostakost2@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:26:19 +0200 Subject: [PATCH] Add jq and tmux expert skills (#414) * Add jq and tmux expert skills for shell workflows Adds two practical, expert-level skills for bash/shell users: - jq: JSON querying and transformation with filtering, mapping, select, reduce, and shell pipeline integration - tmux: session/window/pane management, scripted layouts, automation patterns, and remote SSH workflows Co-Authored-By: Claude Sonnet 4.6 * chore(ci): Refresh PR checks --------- Co-authored-by: kostakost2 Co-authored-by: Claude Sonnet 4.6 Co-authored-by: sickn33 --- skills/jq/SKILL.md | 273 +++++++++++++++++++++++++++++++ skills/tmux/SKILL.md | 370 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 643 insertions(+) create mode 100644 skills/jq/SKILL.md create mode 100644 skills/tmux/SKILL.md diff --git a/skills/jq/SKILL.md b/skills/jq/SKILL.md new file mode 100644 index 00000000..33c044e5 --- /dev/null +++ b/skills/jq/SKILL.md @@ -0,0 +1,273 @@ +--- +name: jq +description: "Expert jq usage for JSON querying, filtering, transformation, and pipeline integration. Practical patterns for real shell workflows." +category: development +risk: safe +source: community +date_added: "2026-03-28" +author: kostakost2 +tags: [jq, json, shell, cli, data-transformation, bash] +tools: [claude, cursor, gemini] +--- + +# jq — JSON Querying and Transformation + +## Overview + +`jq` is the standard CLI tool for querying and reshaping JSON. This skill covers practical, expert-level usage: filtering deeply nested data, transforming structures, aggregating values, and composing `jq` into shell pipelines. Every example is copy-paste ready for real workflows. + +## When to Use This Skill + +- Use when parsing JSON output from APIs, CLI tools (AWS, GitHub, kubectl, docker), or log files +- Use when transforming JSON structure (rename keys, flatten arrays, group records) +- Use when the user needs `jq` inside a bash script or one-liner +- Use when explaining what a complex `jq` expression does + +## How It Works + +`jq` takes a filter expression and applies it to JSON input. Filters compose with pipes (`|`), and `jq` handles arrays, objects, strings, numbers, booleans, and `null` natively. + +### Basic Selection + +```bash +# Extract a field +echo '{"name":"alice","age":30}' | jq '.name' +# "alice" + +# Nested access +echo '{"user":{"email":"a@b.com"}}' | jq '.user.email' + +# Array index +echo '[10, 20, 30]' | jq '.[1]' +# 20 + +# Array slice +echo '[1,2,3,4,5]' | jq '.[2:4]' +# [3, 4] + +# All array elements +echo '[{"id":1},{"id":2}]' | jq '.[]' +``` + +### Filtering with `select` + +```bash +# Keep only matching elements +echo '[{"role":"admin"},{"role":"user"},{"role":"admin"}]' \ + | jq '[.[] | select(.role == "admin")]' + +# Numeric comparison +curl -s https://api.github.com/repos/owner/repo/issues \ + | jq '[.[] | select(.comments > 5)]' + +# Test a field exists and is non-null +jq '[.[] | select(.email != null)]' + +# Combine conditions +jq '[.[] | select(.active == true and .score >= 80)]' +``` + +### Mapping and Transformation + +```bash +# Extract a field from every array element +echo '[{"name":"alice","age":30},{"name":"bob","age":25}]' \ + | jq '[.[] | .name]' +# ["alice", "bob"] + +# Shorthand: map() +jq 'map(.name)' + +# Build a new object per element +jq '[.[] | {user: .name, years: .age}]' + +# Add a computed field +jq '[.[] | . + {senior: (.age > 28)}]' + +# Rename keys +jq '[.[] | {username: .name, email_address: .email}]' +``` + +### Aggregation and Reduce + +```bash +# Sum all values +echo '[1, 2, 3, 4, 5]' | jq 'add' +# 15 + +# Sum a field across objects +jq '[.[].price] | add' + +# Count elements +jq 'length' + +# Max / min +jq 'max_by(.score)' +jq 'min_by(.created_at)' + +# reduce: custom accumulator +echo '[1,2,3,4,5]' | jq 'reduce .[] as $x (0; . + $x)' +# 15 + +# Group by field +jq 'group_by(.department)' + +# Count per group +jq 'group_by(.status) | map({status: .[0].status, count: length})' +``` + +### String Interpolation and Formatting + +```bash +# String interpolation +jq -r '.[] | "\(.name) is \(.age) years old"' + +# Format as CSV (no header) +jq -r '.[] | [.name, .age, .email] | @csv' + +# Format as TSV +jq -r '.[] | [.name, .score] | @tsv' + +# URL-encode a value +jq -r '.query | @uri' + +# Base64 encode +jq -r '.data | @base64' +``` + +### Working with Keys and Paths + +```bash +# List all top-level keys +jq 'keys' + +# Check if key exists +jq 'has("email")' + +# Delete a key +jq 'del(.password)' + +# Delete nested keys from every element +jq '[.[] | del(.internal_id, .raw_payload)]' + +# Recursive descent: find all values for a key anywhere in tree +jq '.. | .id? // empty' + +# Get all leaf paths +jq '[paths(scalars)]' +``` + +### Conditionals and Error Handling + +```bash +# if-then-else +jq 'if .score >= 90 then "A" elif .score >= 80 then "B" else "C" end' + +# Alternative operator: use fallback if null or false +jq '.nickname // .name' + +# try-catch: skip errors instead of halting +jq '[.[] | try .nested.value catch null]' + +# Suppress null output with // empty +jq '.[] | .optional_field // empty' +``` + +### Practical Shell Integration + +```bash +# Read from file +jq '.users' data.json + +# Compact output (no whitespace) for further piping +jq -c '.[]' records.json | while IFS= read -r record; do + echo "Processing: $record" +done + +# Pass a shell variable into jq +STATUS="active" +jq --arg s "$STATUS" '[.[] | select(.status == $s)]' + +# Pass a number +jq --argjson threshold 42 '[.[] | select(.value > $threshold)]' + +# Slurp multiple JSON lines into an array +jq -s '.' records.ndjson + +# Multiple files: slurp all into one array +jq -s 'add' file1.json file2.json + +# Null-safe pipeline from a command +kubectl get pods -o json | jq '.items[] | {name: .metadata.name, status: .status.phase}' + +# GitHub CLI: extract PR numbers +gh pr list --json number,title | jq -r '.[] | "\(.number)\t\(.title)"' + +# AWS CLI: list running instance IDs +aws ec2 describe-instances \ + | jq -r '.Reservations[].Instances[] | select(.State.Name=="running") | .InstanceId' + +# Docker: show container names and images +docker inspect $(docker ps -q) | jq -r '.[] | "\(.Name)\t\(.Config.Image)"' +``` + +### Advanced Patterns + +```bash +# Transpose an object of arrays to an array of objects +# Input: {"names":["a","b"],"scores":[10,20]} +jq '[.names, .scores] | transpose | map({name: .[0], score: .[1]})' + +# Flatten one level +jq 'flatten(1)' + +# Unique by field +jq 'unique_by(.email)' + +# Sort, deduplicate and re-index +jq '[.[] | .name] | unique | sort' + +# Walk: apply transformation to every node recursively +jq 'walk(if type == "string" then ascii_downcase else . end)' + +# env: read environment variables inside jq +export API_KEY=secret +jq -n 'env.API_KEY' +``` + +## Best Practices + +- Always use `-r` (raw output) when passing `jq` results to shell variables or other commands to strip JSON string quotes +- Use `--arg` / `--argjson` to inject shell variables safely — never interpolate shell variables directly into filter strings +- Prefer `map(f)` over `[.[] | f]` for readability +- Use `-c` (compact) for newline-delimited JSON pipelines; omit it for human-readable debugging +- Test filters interactively with `jq -n` and literal input before embedding in scripts +- Use `empty` to drop unwanted elements rather than filtering to `null` + +## Security & Safety Notes + +- `jq` is read-only by design — it cannot write files or execute commands +- Avoid embedding untrusted JSON field values directly into shell commands; always quote or use `--arg` + +## Common Pitfalls + +- **Problem:** `jq` outputs `null` instead of the expected value + **Solution:** Check for typos in key names; use `keys` to inspect actual field names. Remember JSON is case-sensitive. + +- **Problem:** Numbers are quoted as strings in the output + **Solution:** Use `--argjson` instead of `--arg` when injecting numeric values. + +- **Problem:** Filter works in the terminal but fails in a script + **Solution:** Ensure the filter string uses single quotes in the shell to prevent variable expansion. Example: `jq '.field'` not `jq ".field"`. + +- **Problem:** `add` returns `null` on an empty array + **Solution:** Use `add // 0` or `add // ""` to provide a fallback default. + +- **Problem:** Streaming large files is slow + **Solution:** Use `jq --stream` or switch to `jstream`/`gron` for very large files. + +## Related Skills + +- `@bash-pro` — Wrapping jq calls in robust shell scripts +- `@bash-linux` — General shell pipeline patterns +- `@github-automation` — Using jq with GitHub CLI JSON output diff --git a/skills/tmux/SKILL.md b/skills/tmux/SKILL.md new file mode 100644 index 00000000..aa3930c4 --- /dev/null +++ b/skills/tmux/SKILL.md @@ -0,0 +1,370 @@ +--- +name: tmux +description: "Expert tmux session, window, and pane management for terminal multiplexing, persistent remote workflows, and shell scripting automation." +category: development +risk: safe +source: community +date_added: "2026-03-28" +author: kostakost2 +tags: [tmux, terminal, multiplexer, sessions, shell, remote, automation] +tools: [claude, cursor, gemini] +--- + +# tmux — Terminal Multiplexer + +## Overview + +`tmux` keeps terminal sessions alive across SSH disconnects, splits work across multiple panes, and enables fully scriptable terminal automation. This skill covers session management, window/pane layout, keybinding patterns, and using `tmux` non-interactively from shell scripts — essential for remote servers, long-running jobs, and automated workflows. + +## When to Use This Skill + +- Use when setting up or managing persistent terminal sessions on remote servers +- Use when the user needs to run long-running processes that survive SSH disconnects +- Use when scripting multi-pane terminal layouts (e.g., logs + shell + editor) +- Use when automating `tmux` commands from bash scripts without user interaction + +## How It Works + +`tmux` has three hierarchy levels: **sessions** (top level, survives disconnects), **windows** (tabs within a session), and **panes** (splits within a window). Everything is controllable from outside via `tmux ` or from inside via the prefix key (`Ctrl-b` by default). + +### Session Management + +```bash +# Create a new named session +tmux new-session -s work + +# Create detached (background) session +tmux new-session -d -s work + +# Create detached session and start a command +tmux new-session -d -s build -x 220 -y 50 "make all" + +# Attach to a session +tmux attach -t work +tmux attach # attaches to most recent session + +# List all sessions +tmux list-sessions +tmux ls + +# Detach from inside tmux +# Prefix + d (Ctrl-b d) + +# Kill a session +tmux kill-session -t work + +# Kill all sessions except the current one +tmux kill-session -a + +# Rename a session from outside +tmux rename-session -t old-name new-name + +# Switch to another session from outside +tmux switch-client -t other-session + +# Check if a session exists (useful in scripts) +tmux has-session -t work 2>/dev/null && echo "exists" +``` + +### Window Management + +```bash +# Create a new window in the current session +tmux new-window -t work -n "logs" + +# Create a window running a specific command +tmux new-window -t work:3 -n "server" "python -m http.server 8080" + +# List windows +tmux list-windows -t work + +# Select (switch to) a window +tmux select-window -t work:logs +tmux select-window -t work:2 # by index + +# Rename a window +tmux rename-window -t work:2 "editor" + +# Kill a window +tmux kill-window -t work:logs + +# Move window to a new index +tmux move-window -s work:3 -t work:1 + +# From inside tmux: +# Prefix + c — new window +# Prefix + , — rename window +# Prefix + & — kill window +# Prefix + n/p — next/previous window +# Prefix + 0-9 — switch to window by number +``` + +### Pane Management + +```bash +# Split pane vertically (left/right) +tmux split-window -h -t work:1 + +# Split pane horizontally (top/bottom) +tmux split-window -v -t work:1 + +# Split and run a command +tmux split-window -h -t work:1 "tail -f /var/log/syslog" + +# Select a pane by index +tmux select-pane -t work:1.0 + +# Resize panes +tmux resize-pane -t work:1.0 -R 20 # expand right by 20 cols +tmux resize-pane -t work:1.0 -D 10 # shrink down by 10 rows +tmux resize-pane -Z # toggle zoom (fullscreen) + +# Swap panes +tmux swap-pane -s work:1.0 -t work:1.1 + +# Kill a pane +tmux kill-pane -t work:1.1 + +# From inside tmux: +# Prefix + % — split vertical +# Prefix + " — split horizontal +# Prefix + arrow — navigate panes +# Prefix + z — zoom/unzoom current pane +# Prefix + x — kill pane +# Prefix + {/} — swap pane with previous/next +``` + +### Sending Commands to Panes Without Being Attached + +```bash +# Send a command to a specific pane and press Enter +tmux send-keys -t work:1.0 "ls -la" Enter + +# Run a command in a background pane without attaching +tmux send-keys -t work:editor "vim src/main.py" Enter + +# Send Ctrl+C to stop a running process +tmux send-keys -t work:1.0 C-c + +# Send text without pressing Enter (useful for pre-filling prompts) +tmux send-keys -t work:1.0 "git commit -m '" + +# Clear a pane +tmux send-keys -t work:1.0 "clear" Enter + +# Check what's in a pane (capture its output) +tmux capture-pane -t work:1.0 -p +tmux capture-pane -t work:1.0 -p | grep "ERROR" +``` + +### Scripting a Full Workspace Layout + +This is the most powerful pattern: create a fully configured multi-pane workspace from a single script. + +```bash +#!/usr/bin/env bash +set -euo pipefail + +SESSION="dev" + +# Bail if session already exists +tmux has-session -t "$SESSION" 2>/dev/null && { + echo "Session $SESSION already exists. Attaching..." + tmux attach -t "$SESSION" + exit 0 +} + +# Create session with first window +tmux new-session -d -s "$SESSION" -n "editor" -x 220 -y 50 + +# Window 1: editor + test runner side by side +tmux send-keys -t "$SESSION:editor" "vim ." Enter +tmux split-window -h -t "$SESSION:editor" +tmux send-keys -t "$SESSION:editor.1" "npm test -- --watch" Enter +tmux select-pane -t "$SESSION:editor.0" + +# Window 2: server logs +tmux new-window -t "$SESSION" -n "server" +tmux send-keys -t "$SESSION:server" "docker compose up" Enter +tmux split-window -v -t "$SESSION:server" +tmux send-keys -t "$SESSION:server.1" "tail -f logs/app.log" Enter + +# Window 3: general shell +tmux new-window -t "$SESSION" -n "shell" + +# Focus first window +tmux select-window -t "$SESSION:editor" + +# Attach +tmux attach -t "$SESSION" +``` + +### Configuration (`~/.tmux.conf`) + +```bash +# Change prefix to Ctrl-a (screen-style) +unbind C-b +set -g prefix C-a +bind C-a send-prefix + +# Enable mouse support +set -g mouse on + +# Start window/pane numbering at 1 +set -g base-index 1 +setw -g pane-base-index 1 + +# Renumber windows when one is closed +set -g renumber-windows on + +# Increase scrollback buffer +set -g history-limit 50000 + +# Use vi keys in copy mode +setw -g mode-keys vi + +# Faster key repetition +set -s escape-time 0 + +# Reload config without restarting +bind r source-file ~/.tmux.conf \; display "Config reloaded" + +# Intuitive splits: | and - +bind | split-window -h -c "#{pane_current_path}" +bind - split-window -v -c "#{pane_current_path}" + +# New windows open in current directory +bind c new-window -c "#{pane_current_path}" + +# Status bar +set -g status-right "#{session_name} | %H:%M %d-%b" +set -g status-interval 5 +``` + +### Copy Mode and Scrollback + +```bash +# Enter copy mode (scroll up through output) +# Prefix + [ + +# In vi mode: +# / to search forward, ? to search backward +# Space to start selection, Enter to copy +# q to exit copy mode + +# Paste the most recent buffer +# Prefix + ] + +# List paste buffers +tmux list-buffers + +# Show the most recent buffer +tmux show-buffer + +# Save buffer to a file +tmux save-buffer /tmp/tmux-output.txt + +# Load a file into a buffer +tmux load-buffer /tmp/data.txt + +# Pipe pane output to a command +tmux pipe-pane -t work:1.0 "cat >> ~/session.log" +``` + +### Practical Automation Patterns + +```bash +# Idempotent session: create or attach +ensure_session() { + local name="$1" + tmux has-session -t "$name" 2>/dev/null \ + || tmux new-session -d -s "$name" + tmux attach -t "$name" +} + +# Run a command in a new background window and tail its output +run_bg() { + local session="${1:-main}" cmd="${*:2}" + tmux new-window -t "$session" -n "bg-$$" + tmux send-keys -t "$session:bg-$$" "$cmd" Enter +} + +# Wait for a pane to produce specific output (polling) +wait_for_output() { + local target="$1" pattern="$2" timeout="${3:-30}" + local elapsed=0 + while (( elapsed < timeout )); do + tmux capture-pane -t "$target" -p | grep -q "$pattern" && return 0 + sleep 1 + (( elapsed++ )) + done + return 1 +} + +# Kill all background windows matching a name prefix +kill_bg_windows() { + local session="$1" prefix="${2:-bg-}" + tmux list-windows -t "$session" -F "#W" \ + | grep "^${prefix}" \ + | while read -r win; do + tmux kill-window -t "${session}:${win}" + done +} +``` + +### Remote and SSH Workflows + +```bash +# SSH and immediately attach to an existing session +ssh user@host -t "tmux attach -t work || tmux new-session -s work" + +# Run a command on remote host inside a tmux session (fire and forget) +ssh user@host "tmux new-session -d -s deploy 'bash /opt/deploy.sh'" + +# Watch the remote session output from another terminal +ssh user@host -t "tmux attach -t deploy -r" # read-only attach + +# Pair programming: share a session (both users attach to the same session) +# User 1: +tmux new-session -s shared +# User 2 (same server): +tmux attach -t shared +``` + +## Best Practices + +- Always name sessions (`-s name`) in scripts — unnamed sessions are hard to target reliably +- Use `tmux has-session -t name 2>/dev/null` before creating to make scripts idempotent +- Set `-x` and `-y` when creating detached sessions to give panes a proper size for commands that check terminal dimensions +- Use `send-keys ... Enter` for automation rather than piping stdin — it works even when the target pane is running an interactive program +- Keep `~/.tmux.conf` in version control for reproducibility across machines +- Prefer `bind -n` for bindings that don't need the prefix, but only for keys that don't conflict with application shortcuts + +## Security & Safety Notes + +- `send-keys` executes commands in a pane without confirmation — verify the target (`-t session:window.pane`) before use in scripts to avoid sending keystrokes to the wrong pane +- Read-only attach (`-r`) is appropriate when sharing sessions with others to prevent accidental input +- Avoid storing secrets in tmux window/pane titles or environment variables exported into sessions on shared machines + +## Common Pitfalls + +- **Problem:** `tmux` commands from a script fail with "no server running" + **Solution:** Start the server first with `tmux start-server`, or create a detached session before running other commands. + +- **Problem:** Pane size is 0x0 when creating a detached session + **Solution:** Pass explicit dimensions: `tmux new-session -d -s name -x 200 -y 50`. + +- **Problem:** `send-keys` types the text but doesn't run the command + **Solution:** Ensure you pass `Enter` (capital E) as a second argument: `tmux send-keys -t target "cmd" Enter`. + +- **Problem:** Script creates a duplicate session each run + **Solution:** Guard with `tmux has-session -t name 2>/dev/null || tmux new-session -d -s name`. + +- **Problem:** Copy-mode selection doesn't work as expected + **Solution:** Confirm `mode-keys vi` or `mode-keys emacs` is set to match your preference in `~/.tmux.conf`. + +## Related Skills + +- `@bash-pro` — Writing the shell scripts that orchestrate tmux sessions +- `@bash-linux` — General Linux terminal patterns used inside tmux panes +- `@ssh` — Combining tmux with SSH for persistent remote workflows