feat: command registry, alias wrapper scripts, config-ui readability
gates / consistency-and-conventions (push) Successful in 1m28s

- lib/registry.sh: shared query API over POS_* headers (reg_scan, reg_list,
  reg_lookup, reg_tools_in, reg_each, reg_config_scopes/keys). Replaces
  per-consumer sed/grep header parsing.

- bin/pos-tree + bin/pos _pos_category_help(): migrated to registry API.
  Category help now shows [deps: ...] annotations. Tree output preserved.

- New optional headers # POS_DEPS: and # POS_EXAMPLES: in tool metadata.
  Added to pos-network-download (aria2c jq curl), pos-media-sync (lsblk jq),
  pos-system-backup (tar), pos-docker-ps (docker) as initial adopters.

- scripts/gen-docs.sh: extended tools array with deps/examples fields;
  conditional column rendering in gen_dispatch; deps annotation in gen_tree.
  Fixed URL-unsafe // joiner (→ middle dot ·) and \x1f caption delimiter
  collision in config-ui.

- bin/pos-ai-alias: rewrote activation from bash aliases (source-time-frozen)
  to executable wrapper scripts at ~/.local/bin. Staleness eliminated:
  edits apply on next invocation with no shell reload. _alias_sync()
  reconciliation on every subcommand, marker-guarded lifecycle, collision
  refusal, legacy .sh retirement. Fixed dup-table bug (option 4 no-op).

- lib/config-ui.sh: @caption/@[KEY=alt] conditional captions, *providers=<tag>
  tagged wildcards, uniform typography tier (bold/cyan/dim), honest prompt.
  Active provider keys bold, inactive dimmed with reason. Backward-compatible.

- bin/pos-system-uninstall: marker-scan for wrapper script cleanup.

- Docs synced: AGENTS.md (new headers + registry), DOC/SCRIPTS.md (registry
  section + lib list), DOC/POS.md (alias wrapper activation), MAINTENANCE.md
  (M-024). Lint fixed: pos-ai-alias registered in INTERACTIVE_CMDS.

Gates: make gen && make check && make lint = 0 FAIL, 0 WARN
This commit is contained in:
Your Name
2026-08-27 02:30:27 -04:00
parent 4fd3c37c40
commit e969234ca5
20 changed files with 826 additions and 204 deletions
+19 -12
View File
@@ -66,21 +66,27 @@ _pos_category_exists() {
}
_pos_category_help() {
local cat="$1" f
local files=() s d
for f in "$self"/pos-"$cat"-*; do
[ -x "$f" ] || continue
files+=("${f##*/pos-$cat-}")
done
local cat="$1" s s2 extra d sc deps
# Lazy registry load — plain dispatch paths never pay the scan cost.
source "$self/../lib/registry.sh" 2>/dev/null || source "$self/registry.sh"
reg_scan "$self"
local files=()
local t
while IFS= read -r t; do
[ -n "$t" ] || continue
files+=("${t#"$cat"-}")
done < <(reg_tools_in "$cat")
mapfile -t files < <(printf '%s\n' "${files[@]}" | sort -u)
local -A desc subcmds
local sc
local -A desc subcmds _deps
for s in "${files[@]}"; do
d="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$self/pos-$cat-$s" 2>/dev/null)"
[ -n "$d" ] && desc["$s"]="${d#*— }"
sc="$(sed -n '/^# POS_SUBCMDS: /{s/^# POS_SUBCMDS: //;p;q}' "$self/pos-$cat-$s" 2>/dev/null)"
d="$(reg_lookup "$cat-$s" desc)"
[ -n "$d" ] && desc["$s"]="$d"
sc="$(reg_lookup "$cat-$s" subcmds)"
[ -n "$sc" ] && subcmds["$s"]="$sc"
deps="$(reg_lookup "$cat-$s" deps)"
[ -n "$deps" ] && _deps["$s"]="$deps"
done
# Nested sub-tools: pos-<cat>-<a>-<b> lists "b" under <a>.
@@ -113,6 +119,7 @@ _pos_category_help() {
done
[ "$is_nested" -eq 1 ] && continue
printf ' %-28s%s\n' "$s" "${desc[$s]:-}"
[ -n "${_deps[$s]:-}" ] && printf ' [deps: %s]\n' "${_deps[$s]}"
for c in ${subcmds[$s]:-}; do
printf ' %s %s\n' "$s" "$c"
done
@@ -259,7 +266,7 @@ MAIN_LOG="$LOG_DIR/pos.log"
log_cmd() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $* → exit $2" >> "$MAIN_LOG"; }
# Commands that read from stdin interactively — only log invocation
INTERACTIVE_CMDS="docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-sync system-backup system-uninstall share-usb-server share-smb-server share-smb-client share-nfs-client share-nfs-server communication-telegram-listener communication-matrix-listener ai ai-gemini ai-openrouter system-schedule entertainment-config config"
INTERACTIVE_CMDS="docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-sync system-backup system-uninstall share-usb-server share-smb-server share-smb-client share-nfs-client share-nfs-server communication-telegram-listener communication-matrix-listener ai ai-gemini ai-openrouter ai-alias system-schedule entertainment-config config"
for ((i=n-1; i>=0; i--)); do
cmd="pos"
+1 -1
View File
@@ -3,7 +3,7 @@ set -euo pipefail
# POS: ai ask — AI assistant: ask, chat, sessions, capture, models, providers
# POS_SUBCMDS: ask chat sessions capture models providers
# POS_FLAGS: --provider --model --session --system --full --last
# POS_CONFIG: ai | ai.env | AI_PROVIDER=:Provider (gemini or openrouter, default gemini) | *providers | AI_SYSTEM_PROMPT=:Custom system prompt (overrides built-in, empty to reset)
# POS_CONFIG: ai | ai.env | AI_PROVIDER=:Provider (gemini or openrouter, default gemini) | @[AI_PROVIDER=gemini|] Gemini | *providers=gemini | @[AI_PROVIDER=openrouter] OpenRouter | *providers=openrouter | @General | AI_SYSTEM_PROMPT=:Custom system prompt (overrides built-in, empty to reset)
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
+180 -64
View File
@@ -48,35 +48,134 @@ _alias_save() {
chmod 600 "$ENV_FILE"
}
_alias_regen() {
local tmp
tmp="$(mktemp)"
printf '%s\n' "#!/usr/bin/env bash" >"$tmp"
printf '%s\n\n' "# Auto-generated by pos ai alias — do not hand-edit." >>"$tmp"
if [ -f "$ENV_FILE" ]; then
while IFS='|' read -r name provider session prompt _rest; do
[[ "$name" =~ ^[[:space:]]*# ]] && continue
[[ -z "${name// /}" ]] && continue
name="${name## }"; name="${name%% }"
[[ "$name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] || continue
provider="${provider## }"; provider="${provider%% }"
session="${session## }"; session="${session%% }"
[ -z "$session" ] && session="$name"
local escaped_prompt="${prompt//\'/\'\\\'\'}"
printf "alias %s='pos ai %s ask --session %s" "$name" "$provider" "$session" >>"$tmp"
if [ -n "$prompt" ]; then
printf " --system '%s'" "$escaped_prompt" >>"$tmp"
fi
printf "'\n" >>"$tmp"
done < <(grep -v '^[[:space:]]*#' "$ENV_FILE" | grep -v '^[[:space:]]*$' || true)
# Build the pos-ai command for an alias with the prompt safely quoted as
# ONE shell word (printf %q) — shared by regen (stored form) and show
# (copy-pasteable display form). Empty prompt → no --system fragment.
_alias_quote_cmd() {
local provider="$1" session="$2" prompt="${3:-}" q_prompt
printf -v q_prompt '%q' "$prompt"
printf 'pos ai %s ask --session %s' "$provider" "$session"
[ -n "$prompt" ] && printf ' --system %s' "$q_prompt"
return 0
}
# ── Activation artifacts (Option B) ────────────────────────────
# ENV stays the single source of truth; each alias is materialized as an
# executable wrapper script at ~/.local/bin/<name>. Every invocation re-reads
# current bytes, so a stale snapshot (the old sourced-alias failure mode) is
# impossible by construction. No shell sourcing of any kind.
_wrapper_path() {
printf '%s/.local/bin/%s' "$HOME" "$1"
}
# Ownership test: line 2 must carry our generator marker. Files failing this
# test are NEVER overwritten or deleted.
_alias_owned() {
[ -f "$1" ] && sed -n '2p' "$1" 2>/dev/null | grep -q 'Managed by pos ai alias'
}
# Render one wrapper to stdout (args: name provider session prompt).
# The exec line reuses _alias_quote_cmd's double-%q mechanics so the prompt
# lands as exactly ONE shell word; "$@" passes user args through.
_wrapper_render() {
local name="$1" provider="$2" session="$3" prompt="${4:-}"
cat <<WRAPPER_EOF
#!/usr/bin/env bash
# Managed by pos ai alias — regenerated automatically; hand-edits are overwritten.
# Alias: ${name} | provider: ${provider} | session: ${session}
set -euo pipefail
exec $(_alias_quote_cmd "$provider" "$session" "$prompt") "\$@"
WRAPPER_EOF
}
# Atomically install/refresh one wrapper. Skips the write when the rendered
# content already matches (stable mtimes → sync idempotence is observable).
# Pre-commit validation: bash -n on the rendered file; failure keeps previous.
_wrapper_install() { # name provider session prompt
local path="$(_wrapper_path "$1")" tmp
tmp="$(mktemp "${HOME}/.local/bin/.pos-alias.XXXXXX")"
_wrapper_render "$1" "$2" "$3" "$4" >"$tmp"
if cmp -s "$tmp" "$path"; then
rm -f "$tmp"
return 0
fi
if ! bash -n "$tmp" 2>/dev/null; then
warn "Generated alias file has syntax errors — keeping previous version"
warn "Wrapper for '$1' failed syntax check — keeping previous version" >&2
rm -f "$tmp"
return 1
fi
mv "$tmp" "$SH_FILE"
chmod 644 "$SH_FILE"
mv "$tmp" "$path"
chmod 755 "$path"
}
# rc 0 iff ~/.local/bin is on PATH.
_alias_check_path() {
case ":$PATH:" in
*":$HOME/.local/bin:"*) return 0 ;;
*) return 1 ;;
esac
}
# Legacy ~/.config/.../ai-aliases.sh retirement: activation moved to wrapper
# scripts, and a stale sourced alias would shadow them (interactive bash gives
# aliases precedence over PATH lookups). Marker-guarded auto-remove only —
# foreign files are warned about and left untouched.
_alias_retire_legacy_sh() {
[ -f "$SH_FILE" ] || return 0
if ! head -n 3 "$SH_FILE" | grep -q 'Auto-generated by pos ai alias'; then
warn "$SH_FILE was not generated by pos ai alias — left untouched; review manually"
return 0
fi
local stale
stale="$(sed -n 's/^alias \([A-Za-z_][A-Za-z0-9_-]*\)=.*/\1/p' "$SH_FILE" | tr '\n' ' ')"
stale="${stale% }"
rm -f "$SH_FILE"
{
echo "[!] Alias activation moved to executable scripts in ~/.local/bin/ — legacy file removed: $SH_FILE"
[ -n "$stale" ] && echo " Stale sourced aliases shadow the new scripts until cleaned — run: unalias $stale"
echo " (or simply start a new shell)"
} >&2
return 0
}
# Two-way reconciliation on EVERY invocation:
# forward: each ENV entry → render-diff-install (first-run migration,
# create/edit/remove consistency, silent heal of hand-edited wrappers)
# reverse: owned wrappers whose name is not in ENV → deleted (covers remove,
# manual ENV edits, and the empty-set case)
# plus: legacy .sh retirement; PATH guidance when owned wrappers exist but
# ~/.local/bin is absent from PATH (wrappers are written regardless).
_alias_sync() {
_alias_load
local bin_dir="${HOME}/.local/bin" i name f base match any=0
mkdir -p "$bin_dir"
for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do
_wrapper_install "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \
"${_ALIAS_SESSIONS[$i]}" "${_ALIAS_PROMPTS[$i]}" || :
done
for f in "$bin_dir"/*; do
[ -f "$f" ] || continue
_alias_owned "$f" || continue
base="${f##*/}"
match=0
for name in ${_ALIAS_NAMES[@]+"${_ALIAS_NAMES[@]}"}; do
[ "$base" = "$name" ] && { match=1; break; }
done
[ "$match" -eq 1 ] || rm -f "$f"
done
_alias_retire_legacy_sh
if ! _alias_check_path; then
for f in "$bin_dir"/*; do
[ -f "$f" ] && _alias_owned "$f" && { any=1; break; }
done
if [ "$any" -eq 1 ]; then
warn "~/.local/bin is not on your PATH — alias scripts will not resolve by name."
warn " Fix now: export PATH=\"\$HOME/.local/bin:\$PATH\""
warn " Persist it: echo 'export PATH=\"\$HOME/.local/bin:\$PATH\"' >> ~/.profile"
fi
fi
return 0
}
_alias_provider_pick() {
@@ -118,23 +217,27 @@ _alias_prompt_truncate() {
# ── Non-interactive output ─────────────────────────────────────
_alias_list() {
_alias_load
local count=${#_ALIAS_NAMES[@]}
printf 'Aliases (%d):\n' "$count"
# SINGLE alias-table renderer — used by `list` (stdout) and the menu
# pre-render (inside its stderr display block). One source of truth for the
# grid so the two contexts can never drift or duplicate each other.
_alias_table() {
local count=${#_ALIAS_NAMES[@]} i
[ "$count" -eq 0 ] && return 0
printf ' %-12s %-12s %-12s %s\n' "Name" "Provider" "Session" "Prompt"
printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \
"------------------------------------------"
local i
for ((i = 0; i < count; i++)); do
printf ' %-12s %-12s %-12s %s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \
"${_ALIAS_SESSIONS[$i]}" "$(_alias_prompt_truncate "${_ALIAS_PROMPTS[$i]}")"
done
}
_alias_list() {
printf 'Aliases (%d):\n' "${#_ALIAS_NAMES[@]}"
_alias_table
}
_alias_show() {
_alias_load
local idx
idx="$(_alias_find "$1")"
[ "$idx" = "-1" ] && err "Alias '$1' not found"
@@ -144,14 +247,15 @@ _alias_show() {
printf ' %-12s %s\n' "Alias:" "$name"
printf ' %-12s %s\n' "Provider:" "$provider"
printf ' %-12s %s\n' "Session:" "$session"
printf ' %-12s %s\n' "Prompt:" "${prompt:-$(printf '%s' "(default)")}"
# Show the resolved command
local cmd="pos ai $provider ask --session $session"
if [ -n "$prompt" ]; then
local escaped="${prompt//\'/\'\\\'\'}"
cmd="$cmd --system '$escaped'"
if _alias_check_path; then
printf ' %-12s %s\n' "Wrapper:" "$(_wrapper_path "$name")"
else
printf ' %-12s %s\n' "Wrapper:" "(not installed — ~/.local/bin not on PATH)"
fi
printf ' %-12s %s\n' "Command:" "$cmd"
printf ' %-12s %s\n' "Prompt:" "${prompt:-$(printf '%s' "(default)")}"
# Show the resolved command (same quoting mechanism as the generated
# wrapper — what users copy from here pastes into a shell verbatim)
printf ' %-12s %s\n' "Command:" "$(_alias_quote_cmd "$provider" "$session" "$prompt")"
}
# ── Interactive: main menu ─────────────────────────────────────
@@ -164,21 +268,8 @@ _alias_menu() {
if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then
echo "${YELLOW}[!] No aliases defined yet — create one with option 1.${RESET}"
else
local count=${#_ALIAS_NAMES[@]} i
printf ' %-12s %-12s %-12s %s\n' "Name" "Provider" "Session" "Prompt"
printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \
"------------------------------------------"
for ((i = 0; i < count; i++)); do
local p="${_ALIAS_PROMPTS[$i]}"
if [ ${#p} -gt 42 ]; then
p="${p:0:42}…"
fi
printf ' %-12s %-12s %-12s %s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_PROVIDERS[$i]}" \
"${_ALIAS_SESSIONS[$i]}" "$p"
done
printf ' %-12s %-12s %-12s %s\n' "------------" "------------" "------------" \
"------------------------------------------"
printf ' %d alias(es)\n' "$count"
_alias_table
printf ' %d alias(es)\n' "${#_ALIAS_NAMES[@]}"
fi
echo >&2
} >&2
@@ -189,7 +280,10 @@ _alias_menu() {
1) _alias_create ;;
2) _alias_edit ;;
3) _alias_remove ;;
4) _alias_list ;;
4) : ;; # List aliases — the loop's pre-render above IS the current
# table (single renderer, redrawn fresh every iteration);
# option 4 returns to the loop for a fresh render instead
# of printing a second copy (dup-table bug fix).
esac
done
}
@@ -220,6 +314,17 @@ _alias_create() {
[ -n "$preset_name" ] && return 1
name=""; continue
fi
# Collision refusals (never clobber foreign files or real binaries):
# 1. wrapper exists WITH our marker → fine, sync regenerates it
# 2. file exists WITHOUT marker → refuse
# 3. name resolves to another binary on PATH → refuse, naming it
local wpath
wpath="$(_wrapper_path "$name")"
if [ -e "$wpath" ]; then
_alias_owned "$wpath" || err "File '~/.local/bin/$name' already exists and was not created by pos ai alias — pick another name"
elif command -v "$name" >/dev/null 2>&1; then
err "'$name' already exists on PATH as $(command -v "$name") — pick another name"
fi
break
done
@@ -284,8 +389,9 @@ _alias_create() {
_ALIAS_SESSIONS+=("$session")
_ALIAS_PROMPTS+=("$prompt")
_alias_save
_alias_regen
log "Alias '$name' created. Reload shell: source ~/.bashrc" >&2
_alias_sync
log "Alias '$name' created." >&2
log "Available immediately: $(_wrapper_path "$name")" >&2
}
# ── Interactive: edit ──────────────────────────────────────────
@@ -419,8 +525,8 @@ _alias_edit() {
_ALIAS_SESSIONS[$idx]="$new_session"
_ALIAS_PROMPTS[$idx]="$new_prompt"
_alias_save
_alias_regen
log "Alias '$name' updated." >&2
_alias_sync
log "Alias '$name' updated — the change is live on next invocation." >&2
}
# ── Interactive: remove ────────────────────────────────────────
@@ -484,8 +590,9 @@ _alias_remove() {
_ALIAS_SESSIONS=("${new_sessions[@]+"${new_sessions[@]}"}")
_ALIAS_PROMPTS=("${new_prompts[@]+"${new_prompts[@]}"}")
_alias_save
_alias_regen
log "Alias '$name' removed." >&2
_alias_sync
log "Alias '$name' removed — script deleted from $(_wrapper_path "$name")." >&2
log "If the name still autocompletes stale in this shell, run: hash -r" >&2
}
# ── show <name> ───────────────────────────────────────────────
@@ -510,6 +617,12 @@ Subcommands:
list List all aliases (non-interactive, machine-readable)
show <name> Show one alias's details
Activation: every alias is materialized as an executable script at
~/.local/bin/<name>, synced automatically on every invocation — no shell
sourcing required. Changes are live on the next invocation, and the
scripts work identically in interactive shells, scripts, cron, and
non-login ssh sessions.
Options:
-h|--help Show this help.
@@ -526,17 +639,20 @@ EOF
}
# ── Main dispatch ──────────────────────────────────────────────
# Every subcommand syncs first: artifacts always equal ENV truth before any
# subcommand logic runs (migration, healing, retraction — all automatic).
case "${1:-}" in
-h|--help) usage ;;
create) shift; _alias_create "${1:-}" ;;
edit) shift; _alias_edit "${1:-}" ;;
remove) shift; _alias_remove "${1:-}" ;;
list) _alias_list ;;
create) shift; _alias_sync; _alias_create "${1:-}" ;;
edit) shift; _alias_sync; _alias_edit "${1:-}" ;;
remove) shift; _alias_sync; _alias_remove "${1:-}" ;;
list) _alias_sync; _alias_list ;;
show)
[ -n "${2:-}" ] || err "Usage: pos ai alias show <name>"
_alias_sync
_alias_show "$2"
;;
"") _alias_menu ;;
"") _alias_sync; _alias_menu ;;
*) err "Unknown subcommand '$1' (use -h for help)" ;;
esac
+1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
# POS: docker ps — Enhanced container overview (health, IPs, ports, uptime)
# POS_DEPS: docker
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
usage() {
+3
View File
@@ -3,6 +3,9 @@ set -euo pipefail
# POS: media sync — Incremental Music → USB sync (mp3/mp4, add/update only)
# POS_FLAGS: --mp3 --mp4 --source --dry-run
# POS_SUBCMDS: menu
# POS_DEPS: lsblk jq
# POS_EXAMPLES: pos media sync --mp3 | Sync only MP3 files to USB
# POS_EXAMPLES: pos media sync --mp4 --dry-run | Preview MP4 sync without copying
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
+4
View File
@@ -3,6 +3,10 @@ set -euo pipefail
# POS: network download — aria2 RPC daemon + queue control (add/torrent/metalink, watch, limits)
# POS_SUBCMDS: start stop status add torrent metalink list info files peers pause resume remove purge move limit set watch restart retry replace menu
# POS_FLAGS: --dir --out --split --seed --force --upload --gid --tmux
# POS_DEPS: aria2c jq curl
# POS_EXAMPLES: pos network download add https://example.com/file.zip | Enqueue an HTTP download (auto-starts daemon)
# POS_EXAMPLES: pos network download status | Daemon health + global transfer stats
# POS_EXAMPLES: pos network download watch | Live progress view
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/menu-lib.sh" 2>/dev/null || source "$(dirname "$0")/menu-lib.sh"
+1
View File
@@ -4,6 +4,7 @@ set -euo pipefail
# POS_FLAGS: --service --no-encrypt
# POS_SUBCMDS: menu
# POS_CONFIG: notify | notify.env | NOTIFY_PLATFORM=:Comma-separated notify platforms (default telegram) — shared by backup, firewall, share nfs client/server
# POS_DEPS: tar
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/notify.sh" 2>/dev/null || source "$(dirname "$0")/notify.sh"
+20
View File
@@ -95,6 +95,15 @@ scan_tier1() {
# User-local binaries
[ -f "$HOME/.local/bin/pos-ai-hook.sh" ] && found+=("$HOME/.local/bin/pos-ai-hook.sh")
# pos ai alias wrapper scripts (marker-managed)
if [ -d "$HOME/.local/bin" ]; then
local awf
for awf in "$HOME/.local/bin"/*; do
[ -f "$awf" ] || continue
grep -q 'Managed by pos ai alias' "$awf" 2>/dev/null && found+=("$awf")
done
fi
# Completion file
[ -f /usr/local/share/bash-completion/completions/pos.bash ] && found+=("/usr/local/share/bash-completion/completions/pos.bash")
@@ -260,6 +269,17 @@ remove_tier1() {
# User-local binaries
[ -f "$HOME/.local/bin/pos-ai-hook.sh" ] && { rm -f "$HOME/.local/bin/pos-ai-hook.sh" && count=$((count+1)); }
# pos ai alias wrapper scripts (marker-managed only — foreign files untouched)
if [ -d "$HOME/.local/bin" ]; then
local arwf
for arwf in "$HOME/.local/bin"/*; do
[ -f "$arwf" ] || continue
if grep -q 'Managed by pos ai alias' "$arwf" 2>/dev/null; then
rm -f "$arwf" && count=$((count+1))
fi
done
fi
# Completion file
[ -f /usr/local/share/bash-completion/completions/pos.bash ] && { rm -f /usr/local/share/bash-completion/completions/pos.bash && count=$((count+1)); }
+12 -6
View File
@@ -4,6 +4,7 @@ set -euo pipefail
# POS_FLAGS: --depth
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/registry.sh" 2>/dev/null || source "$(dirname "$0")/registry.sh"
usage() {
cat <<EOF
@@ -44,12 +45,17 @@ add() {
meta["$path"]="$type|$desc"
}
for f in "$self"/pos-*; do
[ -x "$f" ] || continue
name="${f##*/pos-}"
desc="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$f")"
desc="${desc#*— }"
sc="$(sed -n '/^# POS_SUBCMDS: /{s/^# POS_SUBCMDS: //;p;q}' "$f")"
reg_scan "$self"
for tool_key in $(reg_list); do
cat="$(reg_lookup "$tool_key" cat)"
desc="$(reg_lookup "$tool_key" desc)"
sc="$(reg_lookup "$tool_key" subcmds)"
deps="$(reg_lookup "$tool_key" deps)"
name="${tool_key}"
if [ -n "$deps" ]; then
desc="$desc [deps: $deps]"
fi
words=(${name//-/ })
parent="/"
for ((i=0; i<${#words[@]}; i++)); do