#!/usr/bin/env bash # Interactive demo launcher for pikl-menu. # Uses pikl to pick a scenario, then runs that scenario in pikl. # # Usage: ./examples/demo.sh set -euo pipefail # Resolve the pikl binary once up front. if [[ -n "${PIKL:-}" ]]; then PIKL_BIN="$PIKL" elif command -v pikl >/dev/null 2>&1; then PIKL_BIN="pikl" else # Build quietly, use the debug binary directly. cargo build --quiet 2>&1 PIKL_BIN="cargo run --quiet --" fi # Wrapper so scenarios can just call `pikl` with args. pikl() { $PIKL_BIN "$@" } # ── Scenario runners ────────────────────────────────────── plain_list() { printf "apple\nbanana\ncherry\ndate\nelderberry\nfig\ngrape\nhoneydew\n" \ | pikl } big_list() { # seq output is wrapped as JSON strings so they get # proper labels (bare numbers parse as JSON numbers # with empty display text). seq 1 500 | sed 's/.*/"&"/' | pikl } json_objects() { cat <<'ITEMS' | pikl {"label": "Arch Linux", "category": "rolling", "init": "systemd"} {"label": "NixOS", "category": "rolling", "init": "systemd"} {"label": "Void Linux", "category": "rolling", "init": "runit"} {"label": "Debian", "category": "stable", "init": "systemd"} {"label": "Alpine", "category": "stable", "init": "openrc"} {"label": "Fedora", "category": "semi-rolling", "init": "systemd"} {"label": "Gentoo", "category": "rolling", "init": "openrc"} ITEMS } custom_label_key() { cat <<'ITEMS' | pikl --label-key name {"name": "Neovim", "type": "editor", "lang": "C/Lua"} {"name": "Helix", "type": "editor", "lang": "Rust"} {"name": "Kakoune", "type": "editor", "lang": "C++"} {"name": "Emacs", "type": "editor", "lang": "Lisp"} {"name": "Vim", "type": "editor", "lang": "C"} ITEMS } git_branches() { if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "not in a git repo" >&2 return 1 fi git branch --format='%(refname:short)' | pikl } git_log_picker() { if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then echo "not in a git repo" >&2 return 1 fi git log --oneline -30 | pikl } file_picker() { find . -maxdepth 3 -type f \ -not -path './.git/*' \ -not -path './target/*' \ -not -name '*.lock' \ | sort \ | pikl } on_select_hook() { printf "one\ntwo\nthree\nfour\nfive\n" \ | pikl --on-select-exec 'echo "you picked: $(cat)" >&2' } mixed_input() { cat <<'ITEMS' | pikl just a plain string {"label": "a json object", "extra": 42} another plain string {"label": "second object", "extra": 99} ITEMS } # ── Phase 3 demos ──────────────────────────────────────── structured_items() { echo "Items have sublabel, icon, and metadata fields." >&2 echo "The output JSON includes action and index context." >&2 echo "" >&2 cat <<'ITEMS' | pikl {"label": "Firefox", "sublabel": "Web Browser", "icon": "firefox", "meta": {"version": "125", "type": "browser"}} {"label": "Neovim", "sublabel": "Text Editor", "icon": "nvim", "meta": {"version": "0.10", "type": "editor"}} {"label": "Alacritty", "sublabel": "Terminal Emulator", "icon": "alacritty", "meta": {"version": "0.13", "type": "terminal"}} {"label": "Thunar", "sublabel": "File Manager", "icon": "thunar", "meta": {"version": "4.18", "type": "filemanager"}} {"label": "mpv", "sublabel": "Media Player", "icon": "mpv", "meta": {"version": "0.37", "type": "media"}} ITEMS } format_template() { echo "Using --format to control display." >&2 echo "Template: '{label} ({sublabel}) v{meta.version}'" >&2 echo "" >&2 cat <<'ITEMS' | pikl --format '{label} ({sublabel}) v{meta.version}' {"label": "Firefox", "sublabel": "Web Browser", "meta": {"version": "125"}} {"label": "Neovim", "sublabel": "Text Editor", "meta": {"version": "0.10"}} {"label": "Alacritty", "sublabel": "Terminal Emulator", "meta": {"version": "0.13"}} {"label": "Thunar", "sublabel": "File Manager", "meta": {"version": "4.18"}} {"label": "mpv", "sublabel": "Media Player", "meta": {"version": "0.37"}} ITEMS } filter_fields_demo() { echo "Using --filter-fields to search sublabel and meta fields." >&2 echo "Try typing 'browser' or 'editor' to filter by sublabel." >&2 echo "" >&2 cat <<'ITEMS' | pikl --filter-fields label,sublabel --format '{label} - {sublabel}' {"label": "Firefox", "sublabel": "Web Browser"} {"label": "Neovim", "sublabel": "Text Editor"} {"label": "Alacritty", "sublabel": "Terminal Emulator"} {"label": "Thunar", "sublabel": "File Manager"} {"label": "mpv", "sublabel": "Media Player"} {"label": "GIMP", "sublabel": "Image Editor"} {"label": "Inkscape", "sublabel": "Vector Graphics Editor"} ITEMS } field_filter_demo() { echo "Field filters in the query: type meta.type:browser to match a field." >&2 echo "Try: meta.type:browser or meta.type:editor or !meta.type:browser" >&2 echo "" >&2 cat <<'ITEMS' | pikl --format '{label} [{meta.type}] {meta.res}' {"label": "Firefox", "meta": {"type": "browser", "res": "n/a"}} {"label": "Chrome", "meta": {"type": "browser", "res": "n/a"}} {"label": "Neovim", "meta": {"type": "editor", "res": "n/a"}} {"label": "Helix", "meta": {"type": "editor", "res": "n/a"}} {"label": "Alacritty", "meta": {"type": "terminal", "res": "n/a"}} {"label": "kitty", "meta": {"type": "terminal", "res": "n/a"}} {"label": "mpv", "meta": {"type": "media", "res": "3840x2160"}} {"label": "vlc", "meta": {"type": "media", "res": "1920x1080"}} ITEMS } exec_hooks_demo() { echo "Exec hooks: --on-hover-exec fires a command on each cursor move." >&2 echo "Watch stderr for hover notifications as you navigate." >&2 echo "" >&2 cat <<'ITEMS' | pikl \ --on-hover-exec 'jq -r ".item.label // empty" | xargs -I{} echo " hovering: {}" >&2' \ --on-select-exec 'jq -r ".item.label // .value // empty" | xargs -I{} echo " selected: {}" >&2' {"label": "Arch Linux", "sublabel": "rolling release"} {"label": "NixOS", "sublabel": "declarative"} {"label": "Void Linux", "sublabel": "runit-based"} {"label": "Debian", "sublabel": "rock solid"} {"label": "Alpine", "sublabel": "musl + busybox"} ITEMS } handler_hook_demo() { echo "Handler hooks: a persistent process receives events on stdin" >&2 echo "and can emit commands on stdout to modify the menu." >&2 echo "" >&2 echo "This demo logs hover events to stderr via a handler script." >&2 echo "The handler stays alive for the menu's lifetime." >&2 echo "" >&2 # Create a temporary handler script local handler handler=$(mktemp /tmp/pikl-handler-XXXXXX.sh) cat > "$handler" <<'HANDLER' #!/bin/bash # Simple handler: logs events to stderr, demonstrates the protocol while IFS= read -r event; do event_type=$(echo "$event" | jq -r '.event // "unknown"') case "$event_type" in hover) label=$(echo "$event" | jq -r '.item.label // "?"') index=$(echo "$event" | jq -r '.index // "?"') echo " handler got hover: $label (index $index)" >&2 ;; filter) text=$(echo "$event" | jq -r '.text // ""') echo " handler got filter: '$text'" >&2 ;; open) echo " handler got open event" >&2 ;; close) echo " handler got close event" >&2 ;; *) echo " handler got: $event_type" >&2 ;; esac done HANDLER chmod +x "$handler" cat <<'ITEMS' | pikl --on-hover "$handler" --on-hover-debounce 100 {"label": "Maple", "sublabel": "Studio founder"} {"label": "Cedar", "sublabel": "Backend dev"} {"label": "Birch", "sublabel": "Frontend dev"} {"label": "Pine", "sublabel": "DevOps"} {"label": "Spruce", "sublabel": "QA"} ITEMS rm -f "$handler" } handler_add_items_demo() { echo "Handler hooks can modify the menu by emitting commands." >&2 echo "This demo adds items when you hover over specific entries." >&2 echo "" >&2 local handler handler=$(mktemp /tmp/pikl-handler-XXXXXX.sh) cat > "$handler" <<'HANDLER' #!/bin/bash # Handler that adds related items on hover while IFS= read -r event; do event_type=$(echo "$event" | jq -r '.event // "unknown"') if [ "$event_type" = "hover" ]; then label=$(echo "$event" | jq -r '.item.label // ""') case "$label" in "Languages") echo '{"action": "add_items", "items": [{"label": " Rust"}, {"label": " Go"}, {"label": " Python"}]}' ;; "Editors") echo '{"action": "add_items", "items": [{"label": " Neovim"}, {"label": " Helix"}, {"label": " Emacs"}]}' ;; esac fi done HANDLER chmod +x "$handler" cat <<'ITEMS' | pikl --on-hover "$handler" --on-hover-debounce 300 {"label": "Languages"} {"label": "Editors"} {"label": "Shells"} ITEMS rm -f "$handler" } pipeline_filter_demo() { echo "Filter pipeline demo: chain filters with |" >&2 echo "Try: 'rolling | !void (exact 'rolling', then exclude void)" >&2 echo "Try: /sys/ (regex: items containing 'sys')" >&2 echo "Try: meta.init:systemd (field filter on init system)" >&2 echo "" >&2 cat <<'ITEMS' | pikl --format '{label} ({meta.category}, {meta.init})' {"label": "Arch Linux", "meta": {"category": "rolling", "init": "systemd"}} {"label": "NixOS", "meta": {"category": "rolling", "init": "systemd"}} {"label": "Void Linux", "meta": {"category": "rolling", "init": "runit"}} {"label": "Debian", "meta": {"category": "stable", "init": "systemd"}} {"label": "Alpine", "meta": {"category": "stable", "init": "openrc"}} {"label": "Fedora", "meta": {"category": "semi-rolling", "init": "systemd"}} {"label": "Gentoo", "meta": {"category": "rolling", "init": "openrc"}} ITEMS } # ── Scenario menu ───────────────────────────────────────── scenarios=( "Plain text list" "Big list (500 items)" "JSON objects (distros)" "Custom --label-key (editors)" "Git branches" "Git log (last 30)" "File picker" "Mixed input (plain + JSON)" "---" "Structured items (sublabel, meta)" "Format template (--format)" "Filter fields (--filter-fields)" "Field filters (meta.type:browser)" "Pipeline + field filters" "---" "Exec hooks (on-hover/select)" "Handler hook (event logging)" "Handler hook (add items on hover)" "---" "on-select-exec hook (legacy)" ) # Map display names to functions run_scenario() { case "$1" in *"Plain text"*) plain_list ;; *"Big list"*) big_list ;; *"JSON objects"*) json_objects ;; *"label-key"*) custom_label_key ;; *"Git branches"*) git_branches ;; *"Git log"*) git_log_picker ;; *"File picker"*) file_picker ;; *"Mixed input"*) mixed_input ;; *"Structured items"*) structured_items ;; *"Format template"*) format_template ;; *"Filter fields"*) filter_fields_demo ;; *"Field filters"*) field_filter_demo ;; *"Pipeline + field"*) pipeline_filter_demo ;; *"Exec hooks"*) exec_hooks_demo ;; *"Handler hook (event"*) handler_hook_demo ;; *"Handler hook (add"*) handler_add_items_demo ;; *"on-select-exec"*) on_select_hook ;; "---") echo "that's a separator, not a scenario" >&2 return 1 ;; *) echo "unknown scenario" >&2 return 1 ;; esac } # ── Main ────────────────────────────────────────────────── main() { echo "pikl demo launcher" >&2 echo "pick a scenario, then interact with it" >&2 echo "" >&2 choice=$(printf '%s\n' "${scenarios[@]}" | pikl) || { echo "cancelled" >&2 exit 1 } # pikl outputs JSON. Strip quotes and extract value for matching. choice=$(echo "$choice" | jq -r '.value // .' 2>/dev/null || echo "$choice" | tr -d '"') echo "" >&2 echo "── running: $choice ──" >&2 echo "" >&2 result=$(run_scenario "$choice") || exit $? echo "" >&2 echo "── result ──" >&2 echo "$result" } main "$@"