diff --git a/AGENT_TODO.md b/AGENT_TODO.md index a8c1e6c..070fd93 100644 --- a/AGENT_TODO.md +++ b/AGENT_TODO.md @@ -42,9 +42,9 @@ summary (newest last). ## Done -- **`pos bank` command bank** — feature (41efc91) + lib registration fix (73d15a2) + main-menu visibility & `run` crash fix (c5d501a) + tab-completion of category-less tools (e23d57e) + multiline command storage/execution fix (11b4a67, v2 escaped format with backward compat). Done 2026-09-12. +- **`pos bank` command bank** — feature (41efc91) + lib registration fix (73d15a2) + main-menu visibility & `run` crash fix (c5d501a) + tab-completion of category-less tools (e23d57e) + multiline command storage/execution fix (11b4a67, v2 escaped format with backward compat). Done 2026-09-12. **2026-09-12: migrated into the `system` category → `pos system bank` (`bin/pos-system-bank`); storage seam (`bank.env`, `BANK_FILE`, v2 format) unchanged.** -- **2026-09-09** — New `pos system alias` tool (Architect→Builder→Reviewer→Writer): persistent command aliases via wrapper scripts in `~/.local/bin/`. Interactive menu (create/edit/remove/list/show), storage at `~/.config/linux_post_install/aliases.env` (pipe-delimited `name|command`), wrapper sync on every invocation, name validation (`^[a-zA-Z][a-zA-Z0-9_-]*$`), ownership markers, collision checks. Docs: POS.md system category + detail block, howto/system.md recipes section. Verified: `bash -n`, `make gen` byte-idempotent, `make check` OK, `make lint` 0 FAIL / 0 WARN. +- **2026-09-09** — New `pos system alias` tool (Architect→Builder→Reviewer→Writer): persistent command aliases via wrapper scripts in `~/.local/bin/`. Interactive menu (create/edit/remove/list/show), storage at `~/.config/linux_post_install/aliases.env` (pipe-delimited `name|command`), wrapper sync on every invocation, name validation (`^[a-zA-Z][a-zA-Z0-9_-]*$`), ownership markers, collision checks. Docs: POS.md system category + detail block, howto/system.md recipes section. Verified: `bash -n`, `make gen` byte-idempotent, `make check` OK, `make lint` 0 FAIL / 0 WARN. **2026-09-12: removed per user request (feature no longer wanted); `bin/pos-system-alias` deleted, docs cleaned up.** - **2026-09-09** — Telegram listener single-instance guard (Toolsmith): `bin/pos-communication-telegram-listener --run` now takes a `flock(1)` on `${XDG_RUNTIME_DIR:-/tmp}/pos-telegram-listener.lock` inside `run_daemon()` (before config load/sync/poll loop) — a second `--run` on the same token fails fast (exit 1, `ERROR: listener already running (single instance) — check: systemctl --user status pos-telegram-listener`), never racing getUpdates (Telegram 409/command stealing). Kernel auto-release → no stale-lock bookkeeping, systemd `Restart=always` restarts clean. `--status` first line now reports `listener: running (single instance lock held)` / `listener: not running` via the same `lock_held()` probe. `flock` dep guard added (`util-linux`). New regression `tests/t-telegram-listener-singleton.sh` (7 checks: first acquires+loops, second exits 1 with exact message, lock releases → third starts clean, status reports both states; stubbed curl/systemctl, sandboxed XDG_RUNTIME_DIR — hermetic, no network). Verified: `bash -n`, `make gen` ×2 byte-idempotent, `make check` OK, `make lint` 0 FAIL / 0 WARN, `make test` green, `git diff --check` clean. diff --git a/DOC/AGENT_Context_Project.md b/DOC/AGENT_Context_Project.md index 3ccc3c2..fbdf1cc 100644 --- a/DOC/AGENT_Context_Project.md +++ b/DOC/AGENT_Context_Project.md @@ -10,19 +10,19 @@ | ## 1. Project Overview | 28–43 | -| ## 2. Directory Structure | 44–218 | -| ## 3. Installation Flow | 219–277 | -| ## 4. The `pos` CLI System | 278–367 | -| ## 5. Shared Library — `lib/common.sh` | 368–399 | -| ## 6. Docker Compose / ScaleTail | 400–442 | -| ## 7. Optional Apps (`apps/`) | 443–472 | -| ## 8. Entertainment Module | 473–486 | -| ## 9. Systemd Services | 487–498 | -| ## 10. Configuration Files | 499–525 | -| ## 11. Coding Conventions | 526–558 | -| ## 12. Development Workflow | 559–611 | -| ## 13. Key File Quick Reference | 612–697 | -| ## 14. Common Tasks for Agents | 698–731 | +| ## 2. Directory Structure | 44–217 | +| ## 3. Installation Flow | 218–276 | +| ## 4. The `pos` CLI System | 277–365 | +| ## 5. Shared Library — `lib/common.sh` | 366–397 | +| ## 6. Docker Compose / ScaleTail | 398–440 | +| ## 7. Optional Apps (`apps/`) | 441–470 | +| ## 8. Entertainment Module | 471–484 | +| ## 9. Systemd Services | 485–496 | +| ## 10. Configuration Files | 497–523 | +| ## 11. Coding Conventions | 524–556 | +| ## 12. Development Workflow | 557–609 | +| ## 13. Key File Quick Reference | 610–694 | +| ## 14. Common Tasks for Agents | 695–728 | ## 1. Project Overview @@ -109,15 +109,14 @@ Linux_post_install/ │ ├── pos-share-smb-server # Manage the Samba server (status, share/unshare exports, users, enable/disable) │ ├── pos-share-usb-server # USB Redirector server control (--ls, --share; prompts when args omitted) │ ├── pos-ssh-load-keys # Load all SSH keys into the agent -│ ├── pos-system-alias # Manage persistent command aliases (wrapper scripts in ~/.local/bin/) │ ├── pos-system-backup # Encrypted (AES-256) folder snapshots (tar + gpg) │ │ [deps: tar] +│ ├── pos-system-bank # Persistent command bank for saving and running shell commands │ ├── pos-system-firewall # Interactive UFW management │ ├── pos-system-health # Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL │ ├── pos-system-schedule # Scheduled jobs: run a command on a timer; notify on threshold/change/error/always or silently │ ├── pos-system-uninstall # Remove pos toolkit binaries, services, shell integration, config, and data │ ├── pos-ai # AI assistant: ask, chat, sessions, capture, models, providers -│ ├── pos-bank # Persistent command bank for saving and running shell commands │ ├── pos-config # Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) │ ├── pos-tree # Show the pos CLI command tree: categories, commands, and subcommands @@ -337,14 +336,13 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst | share | smb-server | `pos-share-smb-server` | Manage the Samba server (status, share/unshare exports, users, enable/disable) | | | | share | usb-server | `pos-share-usb-server` | USB Redirector server control (--ls, --share; prompts when args omitted) | | | | ssh | load-keys | `pos-ssh-load-keys` | Load all SSH keys into the agent | | | -| system | alias | `pos-system-alias` | Manage persistent command aliases (wrapper scripts in ~/.local/bin/) | | | | system | backup | `pos-system-backup` | Encrypted (AES-256) folder snapshots (tar + gpg) | tar | | +| system | bank | `pos-system-bank` | Persistent command bank for saving and running shell commands | | | | system | firewall | `pos-system-firewall` | Interactive UFW management | | | | system | health | `pos-system-health` | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL | | | | system | schedule | `pos-system-schedule` | Scheduled jobs: run a command on a timer; notify on threshold/change/error/always or silently | | | | system | uninstall | `pos-system-uninstall` | Remove pos toolkit binaries, services, shell integration, config, and data | | | | | ai | `pos-ai` | AI assistant: ask, chat, sessions, capture, models, providers | | | -| | bank | `pos-bank` | Persistent command bank for saving and running shell commands | | | | | config | `pos-config` | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) | | | | | tree | `pos-tree` | Show the pos CLI command tree: categories, commands, and subcommands | | | @@ -628,7 +626,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | `lib/menu-lib.sh` | 362 | Category-neutral interactive menu primitives (`menu_guard` tty guard, `menu_run` looping boxed menu, `menu_pick` type-to-filter picker, `menu_ask_value` prompt-with-default via raw-mode bracketed-paste-safe `menu_read_value`; stderr render, fail-closed on non-tty/EOF) — sourced by `share-lib.sh`, open to any category | | `lib/registry.sh` | 199 | Shared query API for POS tool metadata headers (`# POS_*:`) — `reg_scan`/`reg_list`/`reg_lookup`/`reg_each`/config scope helpers; used by `pos-tree` and `gen-docs.sh` | | `lib/yt-lib.sh` | 50 | Shared YouTube helpers for `pos media yt *` (`yt_check_deps`, `yt_validate_url`, `yt_echo_cmd`, `classify_url`) — sourced by `yt-mp3`/`yt-mp4`/`yt-grab`/`yt-subtitles` | -| `lib/bank-lib.sh` | 183 | Shared Command Bank storage helpers for `pos bank` (`bank_load`/`bank_save`/`bank_add`/`bank_remove`/`bank_update`, `{param}` template substitution, v2 `\n`-escaped multiline storage with backward-compat raw load; store `~/.config/linux_post_install/bank.env`) — sourced by `pos-bank` | +| `lib/bank-lib.sh` | 183 | Shared Command Bank storage helpers for `pos system bank` (`bank_load`/`bank_save`/`bank_add`/`bank_remove`/`bank_update`, `{param}` template substitution, v2 `\n`-escaped multiline storage with backward-compat raw load; store `~/.config/linux_post_install/bank.env`) — sourced by `pos-system-bank` | | `bin/flag-reader` | 58 | Inspect flags (list/status/`--raw`) | | `bin/flag-set` | 21 | Set a flag (optionally with a value) | | `bin/flag-clear` | 21 | Unset a flag | @@ -679,17 +677,16 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:` | `bin/pos-share-smb-server` | 441 | Manage the Samba server (status, share/unshare exports, users, enable/disable) | | `bin/pos-share-usb-server` | 362 | USB Redirector server control (--ls, --share; prompts when args omitted) | | `bin/pos-ssh-load-keys` | 31 | Load all SSH keys into the agent | -| `bin/pos-system-alias` | 488 | Manage persistent command aliases (wrapper scripts in ~/.local/bin/) | | `bin/pos-system-backup` | 301 | Encrypted (AES-256) folder snapshots (tar + gpg) | +| `bin/pos-system-bank` | 311 | Persistent command bank for saving and running shell commands | | `bin/pos-system-firewall` | 325 | Interactive UFW management | | `bin/pos-system-health` | 209 | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL | | `bin/pos-system-schedule` | 151 | Scheduled jobs: run a command on a timer; notify on threshold/change/error/always or silently | | `bin/pos-system-uninstall` | 518 | Remove pos toolkit binaries, services, shell integration, config, and data | | `bin/pos-ai` | 714 | AI assistant: ask, chat, sessions, capture, models, providers | -| `bin/pos-bank` | 311 | Persistent command bank for saving and running shell commands | | `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) | | `bin/pos-tree` | 118 | Show the pos CLI command tree: categories, commands, and subcommands | -| `completions/pos.bash` | 331 | Dynamic bash completion | +| `completions/pos.bash` | 330 | Dynamic bash completion | | `apps/install.sh` | 171 | App install/uninstall picker/orchestrator | diff --git a/DOC/POS.md b/DOC/POS.md index 79743e1..45406b9 100644 --- a/DOC/POS.md +++ b/DOC/POS.md @@ -296,22 +296,33 @@ reported as "N videos require sign-in — skipped" (escape hatch: | `pos system health` | `bin/pos-system-health` | Host health dashboard: disk per mount, RAM/swap, failed systemd units, backup age, fail2ban, docker containers. Exits 1 if any check FAILs | Console-only reporter — health itself never sends notifications; forward the output with a wrapper (e.g. the Telegram/Matrix listener map `/status=pos system health`) or schedule it via `pos system schedule` with a `NOTIFY` policy. `HEALTH_BACKUP_MAX_AGE_DAYS` (default 2) and `BACKUP_SERVICE_ROOTS` come from `~/.config/linux_post_install/system.env`; `--help` shows the effective values | | `pos system schedule ` | `bin/pos-system-schedule` | Scheduled jobs — run a command on a timer, notify (or stay silent): `run [name\|all]`, `list`, `config`, `enable [name\|all]`, `disable [name\|all]`, `status`, `migrate`. Each job is a file in `~/.config/linux_post_install/schedule.d/.env` with `INTERVAL` (`5m…59m`, `1h…23h`, `hourly`, `daily`, `weekly`, `OnCalendar=…`), `NOTIFY` policy, optional `MSG`, `RULE` (threshold only), and `COMMAND` = the literal rest of the line (pipes/quotes/`sudo` fine). Policies: `always` (full output every run), `onchange` (send when output differs from the last run; first run always sends), `onerror` (non-zero exit or empty output), `threshold` (first numeric output vs `RULE`, alert on false→true + one recovery — the old event-trigger behavior), `never` (side-effect jobs, no notify) | One systemd **user** timer pair per job (`pos-schedule-.timer` + oneshot `.service`, `Persistent=true`), reconciled on `enable`/`disable`; the legacy single `pos-event-trigger` timer is auto-removed. `migrate` converts a pre-existing `event.env` rule set into `schedule.d/rule-N.env` threshold jobs. `config` is an interactive editor (add/edit/remove/enable/disable, validates interval + threshold); alerts via `lib/notify.sh`; `--dry-run` previews runs/writes/sends; jobs are arbitrary shell commands (chmod 600, same trust model as the Telegram map); starter jobs in `config/schedule.d/` auto-installed no-clobber by postinstall. Bare invocation on a terminal (or the `menu` subcommand) opens an interactive hub over these verbs (list, timer status, run-now, enable, disable, config editor) — a menu run-now asks y/N first and goes through the same `run ` path the systemd timers use | | `pos system uninstall` | `bin/pos-system-uninstall` | Safe, interactive uninstaller for the pos toolkit — scans and removes binaries, services, shell integration, config, and data in three tiers | Tier 1 (always): binaries in `/usr/local/bin/` (pos, pos-*, libs, ai-providers, entertainment plugins, prebuilt, features), systemd services (disable+remove) including runtime-created `~/.config/systemd/user/pos-*` user units, ScaleTail templates + feature-flag store under `/usr/local/share/linux_post_install/`, shell integration in `~/.bashrc` (PATH, completion, pos-ai-hook source), completion file. Tier 2 (`--config`): `~/.config/linux_post_install/` (.env files, schedule.d/, authorized_keys, rclone.conf). Tier 3 (`--data`): `~/.local/share/linux_post_install/` (ai sessions, logs, captured output). Flags: `--yes` (skip prompts, tier 1 only), `--config` (include tier 2), `--data` (include tier 3). Combine all three for nuclear removal. Git repo is never removed | -| `pos system alias` | `bin/pos-system-alias` | Manage persistent command aliases — create, edit, remove, list, and show named aliases that map names to shell commands via executable wrapper scripts in `~/.local/bin/` | Aliases stored in `~/.config/linux_post_install/aliases.env` (pipe-delimited: `name\|command`). Each alias materializes as a wrapper script at `~/.local/bin/` (chmod 755) that runs the mapped command with any arguments forwarded. Wrapper scripts are synced automatically on every invocation; changes are live immediately. Name validation: must start with a letter, then letters/digits/hyphens/underscores. Refuses name collisions with existing files on `~/.local/bin/` (unless pos-owned) and existing binaries on `PATH`. Requires `~/.local/bin` on `PATH` — a warning with a copy-paste fix appears when it isn't | +| `pos system bank` | `bin/pos-system-bank` | Persistent command bank for saving and running shell commands — list, add, show, run, edit, remove | Commands stored in `~/.config/linux_post_install/bank.env` (pipe-delimited: `name\|description\|command`, chmod 600, managed by the tool). Parameterized `{param}` templates are substituted at run time. Interactive menu on a TTY with no args. `BANK_FILE` env seam overrides the path | A scheduled job is the recommended way to run the health dashboard on a timer: a `daily` job with `COMMAND=pos system health` and `NOTIFY=always` sends the dashboard output as the alert — no separate systemd unit needed (the old `pos-health.{service,timer}` units are gone; a legacy install may still have them failed/leftover — disable and remove them). -`pos system alias` in detail: +#### system bank -| Command | Behavior | -|---------|----------| -| `pos system alias` | Interactive menu: create / edit / remove / list aliases; shows the current alias table between picks | -| `pos system alias create [name]` | Interactive 2-step wizard: alias name (must start with a letter, then letters/digits/-/_; unique — collisions with existing files on `~/.local/bin/` or binaries on `PATH` are refused), command (must not contain `\|`); confirms before saving | -| `pos system alias edit [name]` | Edits an existing alias (pick from list or pass the name); shows current values, prompts for the new command (Enter keeps current); saves only if changed | -| `pos system alias remove [name]` | Removes an alias (pick from list or pass the name); confirmation defaults to **no** — removal deletes the wrapper script and cannot be undone | -| `pos system alias list` | Non-interactive: prints all aliases as a Name/Command table (commands truncated at 60 chars) | -| `pos system alias show ` | Prints one alias's details: name, command, wrapper path, and how to test it | +**File:** `bin/pos-system-bank` -Alias storage & activation: records live in `~/.config/linux_post_install/aliases.env` — one `name\|command` line per alias, chmod 600, managed by the tool (do not hand-edit). **Activation needs no shell sourcing**: every `pos system alias` invocation syncs the ENV file against executable wrapper scripts at `~/.local/bin/` (chmod 755) — missing or changed wrappers are atomically rewritten, wrappers pos owns but the ENV no longer lists are deleted, and hand-edited wrappers are healed. Wrapper scripts re-read their bytes on every run, so an edit is **live on the next invocation** (no reload), and the scripts work identically in interactive shells, scripts, cron, and non-login ssh sessions (`~/.local/bin` must stay on `PATH` — a loud warning with a copy-paste fix appears when it isn't). Create refuses name collisions: a foreign file at `~/.local/bin/` and names resolving to another binary on `PATH` are never overwritten. +Persistent command bank for saving and running shell commands. Commands are stored in `~/.config/linux_post_install/bank.env` (pipe-delimited: `name|description|command`, chmod 600, managed by the tool). Supports parameterized templates with `{param}` placeholders that are substituted at run time (quoted for safe shell evaluation). + +On a TTY with no arguments, `pos system bank` opens an interactive menu (list / add / run / edit / remove). + +| Command | Purpose | +|---------|---------| +| `pos system bank list` | List all saved commands | +| `pos system bank add [desc] [cmd]` | Add a new command (interactive for missing args) | +| `pos system bank show ` | Show command details and detected parameters | +| `pos system bank run [key=val …]` | Run a command (interactive for missing params) | +| `pos system bank edit ` | Edit an existing command | +| `pos system bank remove ` | Remove a command | + +Example with parameters: + +``` +pos system bank add convert "Convert video" "ffmpeg -i {input} -crf {quality} {output}" +pos system bank run convert input=clip.mp4 quality=23 output=clip.mkv +``` ### ssh @@ -534,30 +545,6 @@ Feature-flag management CLIs (see [SCRIPTS.md → lib/flags.sh](SCRIPTS.md#libfl | `flag-set [value]` | Set a flag, optionally with a value (requires sudo) | | `flag-clear ` | Unset a flag (requires sudo) | -### bank - -**File:** `bin/pos-bank` - -Persistent command bank for saving and running shell commands. Commands are stored in `~/.config/linux_post_install/bank.env` (pipe-delimited: `name|description|command`). Supports parameterized templates with `{param}` placeholders that are substituted at run time (quoted for safe shell evaluation). - -On a TTY with no arguments, `pos bank` opens an interactive menu (list / add / run / edit / remove). - -| Command | Purpose | -|---------|---------| -| `pos bank list` | List all saved commands | -| `pos bank add [desc] [cmd]` | Add a new command (interactive for missing args) | -| `pos bank show ` | Show command details and detected parameters | -| `pos bank run [key=val …]` | Run a command (interactive for missing params) | -| `pos bank edit ` | Edit an existing command | -| `pos bank remove ` | Remove a command | - -Example with parameters: - -``` -pos bank add convert "Convert video" "ffmpeg -i {input} -crf {quality} {output}" -pos bank run convert input=clip.mp4 quality=23 output=clip.mkv -``` - ### config **File:** `bin/pos-config` diff --git a/DOC/howto/system.md b/DOC/howto/system.md index ecbe93f..41a22ad 100644 --- a/DOC/howto/system.md +++ b/DOC/howto/system.md @@ -1,11 +1,10 @@ # How-To: `pos system` -Host care: encrypted backups, firewall, health dashboard, persistent aliases, and uninstall. Tools: -`alias`, `backup`, `firewall`, `health`, `uninstall`. +Host care: encrypted backups, firewall, health dashboard, and uninstall. Tools: +`backup`, `firewall`, `health`, `uninstall`. | Tool | What it does | |------|--------------| -| `pos system alias` | Manage persistent command aliases (wrapper scripts in `~/.local/bin/`) | | `pos system health` | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker) | | `pos system backup` | gpg-encrypted (AES-256) folder snapshots | | `pos system firewall` | Interactive UFW ("UFW POWER") management | @@ -13,122 +12,6 @@ Host care: encrypted backups, firewall, health dashboard, persistent aliases, an --- -## `pos system alias` — persistent command aliases - -Create named shortcuts for shell commands. Each alias becomes an executable -wrapper script in `~/.local/bin/` that runs the mapped command with any -arguments forwarded. - -### Quick start - -```bash -pos system alias # interactive menu -pos system alias list # show all aliases -pos system alias create # interactive create wizard -pos system alias show restart-dns # show one alias's details -``` - -### Examples - -**Create an alias:** - -```bash -pos system alias create restart-dns -# Step 1: Alias name → restart-dns -# Step 2: Command → sudo systemctl restart systemd-resolved -# Confirm → [y] -# Alias 'restart-dns' created. -# Test it: restart-dns -``` - -**Create more aliases:** - -```bash -pos system alias create exit-google -# Command → pkill -f chrome - -pos system alias create update-all -# Command → sudo apt update && sudo apt upgrade -y - -pos system alias create my-ip -# Command → curl -s ifconfig.me -``` - -**Use them directly** (no `pos` needed — just the alias name): - -```bash -restart-dns # runs: sudo systemctl restart systemd-resolved -exit-google # runs: pkill -f chrome -update-all # runs: sudo apt update && sudo apt upgrade -y -my-ip # runs: curl -s ifconfig.me -restart-dns 1.1.1.1 # arguments are forwarded to the command -``` - -**Edit an alias:** - -```bash -pos system alias edit restart-dns -# Shows current command, prompts for new value (Enter = keep current) -``` - -**Remove an alias:** - -```bash -pos system alias remove restart-dns -# Shows details, asks for confirmation (default: no) -``` - -**List all aliases:** - -```bash -pos system alias list -# Name Command -# ---------------- ---------------------------------------- -# restart-dns sudo systemctl restart systemd-resolved -# exit-google pkill -f chrome -``` - -### How it works - -- Aliases are stored in `~/.config/linux_post_install/aliases.env` - (pipe-delimited: `name|command`, chmod 600). -- Each alias is materialized as an executable wrapper at - `~/.local/bin/` (chmod 755). -- Wrappers are synced automatically on every `pos system alias` invocation - — edits are live on the next run. -- Name validation: must start with a letter, then letters/digits/hyphens/ - underscores. Collisions with existing files or PATH binaries are refused. - -### PATH requirement - -`~/.local/bin` must be on your `PATH` for alias scripts to resolve by name. -If it isn't, you'll see a warning with a fix: - -```bash -export PATH="$HOME/.local/bin:$PATH" -# Persist it: -echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.profile -``` - -### Recipes - -- **DNS restart shortcut:** `pos system alias create restart-dns` - with command `sudo systemctl restart systemd-resolved`. -- **Quick app launcher:** `pos system alias create open-code` - with command `code ~/projects`. -- **Custom backup alias:** `pos system alias create snap-docs` - with command `pos system backup ~/Documents`. - -### Troubleshooting - -- `Alias 'X' already exists` → use `pos system alias edit X` instead. -- `File '~/.local/bin/X' already exists` → pick a different name (pos - won't overwrite non-pos-owned files). -- `~/.local/bin is not on your PATH` → add it to `~/.profile` (see above). -- Alias name autocompletes stale after removal → run `hash -r`. - ---- - ## `pos system health` — host health dashboard ```bash @@ -357,4 +240,3 @@ confirmation. The git repo is **never** removed — delete it manually if desire - Reference: [DOC/POS.md → system](../POS.md) - Notify platform config: [communication.md](communication.md) - Backup roots shared with health: `system.env` ([DOC/POS.md](../POS.md)) -- Alias storage: `~/.config/linux_post_install/aliases.env` ([DOC/POS.md → pos system alias](../POS.md#pos-system-alias-in-detail)) diff --git a/bin/pos b/bin/pos index 3dd7246..1274a46 100755 --- a/bin/pos +++ b/bin/pos @@ -28,7 +28,7 @@ _pos_commands() { # ── Category list (sorted, filename-derived) ───────────────────── # Nested sub-tools (pos--- where pos-- exists) are # omitted from the cheat-sheet line — they are shown under their parent tool. -# Category-less tools (pos-bank, pos-config, pos-tree) are emitted as their +# Category-less tools (pos-config, pos-tree) are emitted as their # own line with an empty subcommand column; a name already present as a # category (pos-ai vs the ai category built from pos-ai-*) is NOT duplicated. _pos_category_list() { @@ -214,7 +214,7 @@ EXAMPLES pos tree Show the command tree (categories + subcommands) - pos bank run Run a saved command from the bank + pos system bank run Run a saved command from the bank pos docker vbox create lab1 Create disposable Docker VM pos docker vbox create lab1 --dir . Create VM using current directory @@ -274,7 +274,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="bank docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-yt-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-llamacpp ai-alias system-alias system-schedule entertainment-config config" +INTERACTIVE_CMDS="system-bank docker-compose docker-vbox network-hotspot system-firewall media-mp4 media-yt-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-llamacpp ai-alias system-schedule entertainment-config config" for ((i=n-1; i>=0; i--)); do cmd="pos" diff --git a/bin/pos-system-alias b/bin/pos-system-alias deleted file mode 100755 index b923ea1..0000000 --- a/bin/pos-system-alias +++ /dev/null @@ -1,488 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -# POS: system alias — Manage persistent command aliases (wrapper scripts in ~/.local/bin/) -# POS_SUBCMDS: create edit remove list show - -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" - -# ── Paths & constants ────────────────────────────────────────── -ENV_FILE="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}/aliases.env" - -# ── Core helpers ─────────────────────────────────────────────── - -# Load aliases from ENV_FILE into parallel arrays. -# Loop vars use _a* prefix to avoid dynamic-scope collisions with callers. -_alias_load() { - _ALIAS_NAMES=(); _ALIAS_COMMANDS=() - [ -f "$ENV_FILE" ] || return 0 - local _an _ac - while IFS='|' read -r _an _ac; do - [[ "$_an" =~ ^[[:space:]]*# ]] && continue - [[ -z "${_an// /}" ]] && continue - _an="${_an## }"; _an="${_an%% }" - [[ "$_an" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] || continue - _ac="${_ac## }"; _ac="${_ac%% }" - _ALIAS_NAMES+=("$_an") - _ALIAS_COMMANDS+=("$_ac") - done < <(grep -v '^[[:space:]]*#' "$ENV_FILE" | grep -v '^[[:space:]]*$' || true) -} - -# Save parallel arrays back to ENV_FILE (atomic overwrite). -_alias_save() { - mkdir -p "$(dirname "$ENV_FILE")" - { - printf '%s\n' "# System aliases — managed by pos system alias (do not hand-edit)" - printf '%s\n' "# Format: alias_name|command" - local i - for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do - printf '%s|%s\n' "${_ALIAS_NAMES[$i]}" "${_ALIAS_COMMANDS[$i]}" - done - } >"$ENV_FILE" - chmod 600 "$ENV_FILE" -} - -# ── Wrapper scripts ──────────────────────────────────────────── - -_wrapper_path() { - printf '%s/.local/bin/%s' "$HOME" "$1" -} - -# Ownership test: line 2 must carry our generator marker. -_alias_owned() { - [ -f "$1" ] && sed -n '2p' "$1" 2>/dev/null | grep -q 'Managed by pos system alias' -} - -# Render one wrapper to stdout (args: name command). -_wrapper_render() { - local name="$1" command="$2" - cat <"$tmp" - if cmp -s "$tmp" "$path" 2>/dev/null; then - rm -f "$tmp" - return 0 - fi - if ! bash -n "$tmp" 2>/dev/null; then - warn "Wrapper for '$name' failed syntax check — keeping previous version" >&2 - rm -f "$tmp" - return 1 - fi - 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 -} - -# Two-way reconciliation on every invocation: -# forward: each ENV entry → render-diff-install -# reverse: owned wrappers whose name is not in ENV → deleted -# plus: PATH guidance when owned wrappers exist but ~/.local/bin is absent -_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_COMMANDS[$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 - 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_find() { - local name="$1" i - for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do - if [ "${_ALIAS_NAMES[$i]}" = "$name" ]; then - echo "$i" - return 0 - fi - done - echo "-1" - return 0 -} - -_alias_name_valid() { - [[ "$1" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] -} - -# Truncate a command string for display (inline pipes, redirects). -_command_truncate() { - local s="$1" max="${2:-42}" - s="${s//$'\n'/ }" - if [ ${#s} -gt "$max" ]; then - printf '%s…' "${s:0:max}" - else - printf '%s' "$s" - fi -} - -# ── Non-interactive output ───────────────────────────────────── - -_alias_table() { - local count=${#_ALIAS_NAMES[@]} i - [ "$count" -eq 0 ] && return 0 - printf ' %-16s %s\n' "Name" "Command" - printf ' %-16s %s\n' "----------------" "----------------------------------------" - for ((i = 0; i < count; i++)); do - printf ' %-16s %s\n' "${_ALIAS_NAMES[$i]}" "$(_command_truncate "${_ALIAS_COMMANDS[$i]}" 60)" - done -} - -_alias_list() { - printf 'Aliases (%d):\n' "${#_ALIAS_NAMES[@]}" - _alias_table -} - -_alias_show() { - local idx - idx="$(_alias_find "$1")" - [ "$idx" = "-1" ] && err "Alias '$1' not found" - local name="${_ALIAS_NAMES[$idx]}" command="${_ALIAS_COMMANDS[$idx]}" - printf ' %-12s %s\n' "Alias:" "$name" - printf ' %-12s %s\n' "Command:" "$command" - 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' "Test:" "$name" -} - -# ── Interactive: main menu ───────────────────────────────────── - -_alias_menu() { - menu_guard || return 1 - while true; do - { - _alias_load - if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then - echo "${YELLOW}[!] No aliases defined yet — create one with option 1.${RESET}" - else - _alias_table - printf ' %d alias(es)\n' "${#_ALIAS_NAMES[@]}" - fi - echo >&2 - } >&2 - local choice - choice="$(menu_run "System Aliases" "Create new alias" "Edit existing alias" \ - "Remove alias" "List aliases")" || return 0 - case "$choice" in - 1) _alias_create ;; - 2) _alias_edit ;; - 3) _alias_remove ;; - 4) : ;; # List aliases — the loop's pre-render IS the current table - esac - done -} - -# ── Interactive: create ──────────────────────────────────────── - -_alias_create() { - local preset_name="${1:-}" - section "Create System Alias" >&2 - - # Step 1: Alias name - local name="$preset_name" - while true; do - if [ -z "$name" ]; then - step 1 2 "Alias Name" >&2 - name="$(menu_ask_value "Alias name" "")" || return 0 - fi - [ -z "$name" ] && { warn "Alias name cannot be empty" >&2; name=""; continue; } - if ! _alias_name_valid "$name"; then - warn "Invalid name '$name' — use letters, digits, hyphens, underscores (start with a letter)" >&2 - name=""; continue - fi - _alias_load - local existing - existing="$(_alias_find "$name")" - if [ "$existing" != "-1" ]; then - warn "Alias '$name' already exists — use 'pos system alias edit $name' instead" >&2 - [ -n "$preset_name" ] && return 1 - name=""; continue - fi - # Collision: wrapper exists without our marker → refuse - 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 system alias — pick another name" - fi - # Collision: name resolves to another binary on PATH → refuse - if command -v "$name" >/dev/null 2>&1; then - err "'$name' already exists on PATH as $(command -v "$name") — pick another name" - fi - break - done - - # Step 2: Command - local command="" - while true; do - step 2 2 "Command" >&2 - command="$(menu_ask_value "Command to execute" "")" || return 0 - [ -z "$command" ] && { warn "Command cannot be empty" >&2; continue; } - [[ "$command" == *'|'* ]] || break - warn "Command must not contain '|' characters" >&2 - command="" - done - - # Confirmation - { - echo "────────────────────────────────────────────" - printf ' Create alias '\''%s'\''?\n' "$name" - printf ' Command: %s\n' "$command" - echo "────────────────────────────────────────────" - } >&2 - if ! confirm "Create alias '$name'?" y; then - log "Aborted." >&2 - return 0 - fi - - _alias_load - _ALIAS_NAMES+=("$name") - _ALIAS_COMMANDS+=("$command") - _alias_save - _alias_sync - log "Alias '$name' created." >&2 - log "Test it: $name" >&2 - log "Available immediately: $(_wrapper_path "$name")" >&2 -} - -# ── Interactive: edit ────────────────────────────────────────── - -_alias_edit() { - local preset_name="${1:-}" - _alias_load - if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then - warn "No aliases to edit — create one first" >&2 - return 0 - fi - - local name="$preset_name" - if [ -z "$name" ]; then - section "Edit System Alias" >&2 - local display_items=() i - for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do - local c="${_ALIAS_COMMANDS[$i]}" - if [ ${#c} -gt 30 ]; then - c="${c:0:30}…" - fi - display_items+=("${_ALIAS_NAMES[$i]} → ${c}") - done - local picked - picked="$(menu_pick "Pick alias to edit" "${display_items[@]}")" || return 0 - name="${_ALIAS_NAMES[$((picked - 1))]}" - fi - - local idx - idx="$(_alias_find "$name")" - if [ "$idx" = "-1" ]; then - err "Alias '$name' not found" - fi - - # Show current values - { - echo " Current values for '$name':" - printf ' Command: %s\n' "${_ALIAS_COMMANDS[$idx]}" - echo >&2 - } >&2 - - local new_command="${_ALIAS_COMMANDS[$idx]}" - local changed=0 - - # Edit command - step 1 1 "Command" >&2 - local default_display="${_ALIAS_COMMANDS[$idx]}" - [ ${#default_display} -gt 60 ] && default_display="${default_display:0:60}…" - local tmp_command - tmp_command="$(menu_ask_value "Command to execute" "$default_display")" || return 0 - if [ -n "$tmp_command" ]; then - if [[ "$tmp_command" == *'|'* ]]; then - warn "Command must not contain '|' characters" >&2 - return 0 - fi - if [ "$tmp_command" != "${_ALIAS_COMMANDS[$idx]}" ]; then - new_command="$tmp_command" - changed=1 - fi - fi - - # No changes? - if [ "$changed" -eq 0 ]; then - log "No changes — nothing to save." >&2 - return 0 - fi - - # Show diff summary - { - echo "────────────────────────────────────────────" - printf ' Save changes to '\''%s'\''?\n' "$name" - local tag_c - [ "$new_command" = "${_ALIAS_COMMANDS[$idx]}" ] && tag_c="(unchanged)" || tag_c="(changed)" - printf ' Command: %s %s\n' "$new_command" "$tag_c" - echo "────────────────────────────────────────────" - } >&2 - if ! confirm "Save changes to '$name'?" y; then - log "Discarded." >&2 - return 0 - fi - - _alias_load - _ALIAS_COMMANDS[$idx]="$new_command" - _alias_save - _alias_sync - log "Alias '$name' updated — the change is live on next invocation." >&2 -} - -# ── Interactive: remove ──────────────────────────────────────── - -_alias_remove() { - local preset_name="${1:-}" - _alias_load - if [ ${#_ALIAS_NAMES[@]} -eq 0 ]; then - warn "No aliases to remove" >&2 - return 0 - fi - - local name="$preset_name" - if [ -z "$name" ]; then - section "Remove System Alias" >&2 - local display_items=() i - for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do - local c="${_ALIAS_COMMANDS[$i]}" - if [ ${#c} -gt 30 ]; then - c="${c:0:30}…" - fi - display_items+=("${_ALIAS_NAMES[$i]} → ${c}") - done - local picked - picked="$(menu_pick "Pick alias to remove" "${display_items[@]}")" || return 0 - name="${_ALIAS_NAMES[$((picked - 1))]}" - fi - - local idx - idx="$(_alias_find "$name")" - if [ "$idx" = "-1" ]; then - err "Alias '$name' not found" - fi - - # Show alias detail - { - echo " Alias: $name" - printf ' Command: %s\n' "${_ALIAS_COMMANDS[$idx]}" - echo >&2 - } >&2 - - if ! confirm "Remove alias '$name'? This cannot be undone." n; then - log "Cancelled." >&2 - return 0 - fi - - _alias_load - local new_names=() new_commands=() i - for ((i = 0; i < ${#_ALIAS_NAMES[@]}; i++)); do - if [ "${_ALIAS_NAMES[$i]}" != "$name" ]; then - new_names+=("${_ALIAS_NAMES[$i]}") - new_commands+=("${_ALIAS_COMMANDS[$i]}") - fi - done - _ALIAS_NAMES=("${new_names[@]+"${new_names[@]}"}") - _ALIAS_COMMANDS=("${new_commands[@]+"${new_commands[@]}"}") - _alias_save - _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 -} - -# ── Usage ────────────────────────────────────────────────────── - -usage() { - cat <<'EOF' -Usage: pos system alias [subcommand] [args] - -Manage persistent command aliases — create, edit, remove, list, and show -named aliases. Each alias maps a name to a shell command, materialized as -an executable wrapper script in ~/.local/bin/. - -Subcommands: - (no args) Interactive menu - create [name] Create a new alias (interactive prompts) - edit [name] Edit an existing alias (interactive, Enter = keep) - remove [name] Remove an alias (with confirmation) - list List all aliases (non-interactive, machine-readable) - show Show one alias's details - -Activation: every alias is materialized as an executable script at -~/.local/bin/, synced automatically on every invocation. Changes are -live on the next invocation. - -Options: - -h|--help Show this help. - -Examples: - pos system alias # interactive menu - pos system alias list # show all aliases - pos system alias create # interactive create - pos system alias create restart-dns # create 'restart-dns' alias - pos system alias edit restart-dns # edit the 'restart-dns' alias - pos system alias remove restart-dns # remove 'restart-dns' (with confirm) - pos system alias show restart-dns # show alias details -EOF - exit 0 -} - -# ── Main dispatch ────────────────────────────────────────────── -# Every subcommand syncs first: artifacts always equal ENV truth before any -# subcommand logic runs. - -case "${1:-}" in - -h|--help) usage ;; - 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 system alias show " - _alias_sync - _alias_show "$2" - ;; - "") _alias_sync; _alias_menu ;; - *) err "Unknown subcommand '$1' (use -h for help)" ;; -esac diff --git a/bin/pos-bank b/bin/pos-system-bank similarity index 93% rename from bin/pos-bank rename to bin/pos-system-bank index bd5efae..db4d81c 100755 --- a/bin/pos-bank +++ b/bin/pos-system-bank @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -# POS: bank — Persistent command bank for saving and running shell commands +# POS: system bank — Persistent command bank for saving and running shell commands # POS_SUBCMDS: list add show run edit remove source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh" @@ -9,7 +9,7 @@ source "$(dirname "$0")/../lib/bank-lib.sh" 2>/dev/null || source "$(dirname "$0 usage() { cat <<'EOF' -Usage: pos bank [subcommand] [args] +Usage: pos system bank [subcommand] [args] Persistent command bank for saving and running shell commands. @@ -22,12 +22,12 @@ Subcommands: remove Remove a command Interactive menu: - pos bank (no args on a terminal) + pos system bank (no args on a terminal) Examples: - pos bank add disk-info "Disk usage" "df -h && du -sh /mnt/12T" - pos bank run disk-info - pos bank run convert-video input=/path/to/video.mp4 quality=23 output=/path/out.mp4 + pos system bank add disk-info "Disk usage" "df -h && du -sh /mnt/12T" + pos system bank run disk-info + pos system bank run convert-video input=/path/to/video.mp4 quality=23 output=/path/out.mp4 EOF exit 0 } @@ -41,7 +41,7 @@ _pick_command() { bank_load local count="${#BANK_NAMES[@]}" if [ "$count" -eq 0 ]; then - warn "Command bank is empty — add one with: pos bank add \"desc\" \"cmd\"" + warn "Command bank is empty — add one with: pos system bank add \"desc\" \"cmd\"" return 1 fi local -a items=() @@ -63,7 +63,7 @@ cmd_list() { bank_load local count="${#BANK_NAMES[@]}" if [ "$count" -eq 0 ]; then - log "Command bank is empty — add one with: pos bank add \"desc\" \"cmd\"" + log "Command bank is empty — add one with: pos system bank add \"desc\" \"cmd\"" return 0 fi echo @@ -103,7 +103,7 @@ cmd_add() { bank_load local i for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do - [[ "${BANK_NAMES[$i]}" == "$name" ]] && err "Command '$name' already exists (use 'pos bank edit' to modify)" + [[ "${BANK_NAMES[$i]}" == "$name" ]] && err "Command '$name' already exists (use 'pos system bank edit' to modify)" done bank_add "$name" "$desc" "$cmd" ok "Saved: $name" @@ -111,7 +111,7 @@ cmd_add() { # ── cmd_show ───────────────────────────────────────────────────── cmd_show() { - [ -n "${1:-}" ] || err "Usage: pos bank show " + [ -n "${1:-}" ] || err "Usage: pos system bank show " local name="$1" bank_load bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name" diff --git a/completions/pos.bash b/completions/pos.bash index 44a2295..d7ca16f 100644 --- a/completions/pos.bash +++ b/completions/pos.bash @@ -49,11 +49,10 @@ _pos_subcmds[share-nfs-client]="mount unmount list persist unpersist menu" _pos_subcmds[share-nfs-server]="status share unshare list reload enable disable menu" _pos_subcmds[share-smb-client]="mount unmount list persist unpersist menu" _pos_subcmds[share-smb-server]="status share unshare list adduser deluser reload enable disable menu" -_pos_subcmds[system-alias]="create edit remove list show" _pos_subcmds[system-backup]="menu" +_pos_subcmds[system-bank]="list add show run edit remove" _pos_subcmds[system-schedule]="run list config enable disable status migrate menu" _pos_subcmds[ai]="ask chat sessions capture models providers llamacpp alias gemini hf openrouter server" -_pos_subcmds[bank]="list add show run edit remove" # GEN:END possubcmds # GEN:START posconfigscopes declare -a _pos_config_scopes=(ai compose entertainment grab matrix notify scrcpy system telegram ytsync) diff --git a/lib/bank-lib.sh b/lib/bank-lib.sh index 6e06660..3fac411 100644 --- a/lib/bank-lib.sh +++ b/lib/bank-lib.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # lib/bank-lib.sh — shared storage helpers for the Command Bank. -# Sourced by bin/pos-bank. Uses err() from lib/common.sh. +# Sourced by bin/pos-system-bank. Uses err() from lib/common.sh. # # Storage: ~/.config/linux_post_install/bank.env # Format: name|description|command @@ -11,7 +11,7 @@ # * Defines ONLY bank_* functions — sourcing never clobbers a tool's helpers. # * Requires common.sh to be sourced by the CALLER. # * NEVER exits — return codes only. -# * Performs NO interactive prompts (those stay in bin/pos-bank). +# * Performs NO interactive prompts (those stay in bin/pos-system-bank). CONFIG_DIR="${CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/linux_post_install}" BANK_FILE="${BANK_FILE:-${CONFIG_DIR}/bank.env}" diff --git a/tests/README.md b/tests/README.md index 6fa6df3..46d806b 100644 --- a/tests/README.md +++ b/tests/README.md @@ -57,4 +57,4 @@ silently. | `t-share-mountpoint.sh` | share-client `ask_mountpoint` UX: existing/new/declined/rejected paths, confirm gate, mkdir side effects, non-TTY stdin contract, static `n`→`t` guards | | `t-pos-media-yt.sh` | unified `pos media yt` suite: dispatcher + forwarder resolution, shared yt-lib helpers, yt-mp3/mp4/grab/subtitles flags, dry-run deps, `YT_OUT_DIR` seam, `GRAB_DEFAULT` config, negative controls (unsafe-URL no-expansion, `--lang en,ar` single arg, txt timestamp-stripping) | | `t-telegram-listener-singleton.sh` | Telegram listener single-instance guard: first `--run` acquires the flock, second `--run` fails fast with the exact message, lock auto-releases so the next start is clean, `--status` reports the lock state | -| `t-bank.sh` | Command Bank: bank-lib.sh unit tests (add/remove/update/find/get/list/count/valid/extract_params/substitute_params, multiline `\n` storage round-trip, literal-`\n` escape round-trip, v1 backward compat) + pos-bank CLI integration (help, list, add, show, run, remove, params, invalid name, multiline show/run) | \ No newline at end of file +| `t-bank.sh` | Command Bank: bank-lib.sh unit tests (add/remove/update/find/get/list/count/valid/extract_params/substitute_params, multiline `\n` storage round-trip, literal-`\n` escape round-trip, v1 backward compat) + pos system bank CLI integration (help, list, add, show, run, remove, params, invalid name, multiline show/run) | \ No newline at end of file diff --git a/tests/t-bank.sh b/tests/t-bank.sh index 95e5a08..b87bf8d 100755 --- a/tests/t-bank.sh +++ b/tests/t-bank.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -# t-bank.sh — Command Bank feature: bank-lib.sh unit tests + pos-bank CLI +# t-bank.sh — Command Bank feature: bank-lib.sh unit tests + pos system bank CLI # integration tests. # # Storage contract: pipe-delimited name|description|command in $BANK_FILE. @@ -233,7 +233,7 @@ HELPER ( BANK_FILE="$sandbox/a18.bank.env" cat > "$BANK_FILE" <<'BANK' -# Command Bank — managed by pos bank (do not hand-edit) +# Command Bank — managed by pos system bank (do not hand-edit) # Format: name|description|command real|Real command|echo real @@ -295,7 +295,7 @@ SCRIPT ( BANK_FILE="$sandbox/a21.bank.env" cat > "$BANK_FILE" <<'BANK' -# Command Bank — managed by pos bank (do not hand-edit) +# Command Bank — managed by pos system bank (do not hand-edit) # Format: name|description|command raw|Raw echo|echo 'a\b' BANK @@ -311,7 +311,7 @@ BANK ( BANK_FILE="$sandbox/a22.bank.env" cat > "$BANK_FILE" <<'BANK' -# Command Bank — managed by pos bank (do not hand-edit) +# Command Bank — managed by pos system bank (do not hand-edit) # Format: name|description|command ts-google|Tailscale Google|tailscale status --json >/tmp/ts.json && grep -q '"ExitNodeStatus":null' /tmp/ts.json && tailscale set --exit-node=google || tailscale set --exit-node= BANK @@ -328,116 +328,116 @@ BANK || printf ' FAIL old-format entry round-trips through re-save\n' # ═══════════════════════════════════════════════════════════════ - # Part B: pos-bank CLI integration tests + # Part B: pos system bank CLI integration tests # ═══════════════════════════════════════════════════════════════ - local pos_bank="$ROOT/bin/pos-bank" + local pos_bank="$ROOT/bin/pos-system-bank" - # B1: pos bank --help shows usage + # B1: pos system bank --help shows usage test_run env BANK_FILE="$sandbox/b1.bank.env" "$pos_bank" --help - check_rc "pos bank --help exits 0" 0 "$TR_RC" - check_contains "pos bank --help shows Usage" "Usage:" "$TR_OUT" - check_contains "pos bank --help mentions subcommands" "Subcommands:" "$TR_OUT" + check_rc "pos system bank --help exits 0" 0 "$TR_RC" + check_contains "pos system bank --help shows Usage" "Usage:" "$TR_OUT" + check_contains "pos system bank --help mentions subcommands" "Subcommands:" "$TR_OUT" - # B2: pos bank list on empty bank shows empty message + # B2: pos system bank list on empty bank shows empty message : > "$sandbox/b2.bank.env" test_run env BANK_FILE="$sandbox/b2.bank.env" "$pos_bank" list - check_rc "pos bank list empty exits 0" 0 "$TR_RC" - check_contains "pos bank list empty message" "Command bank is empty" "$TR_OUT" + check_rc "pos system bank list empty exits 0" 0 "$TR_RC" + check_contains "pos system bank list empty message" "Command bank is empty" "$TR_OUT" - # B3: pos bank add — adds a command, verify in bank.env + # B3: pos system bank add — adds a command, verify in bank.env : > "$sandbox/b3.bank.env" test_run env BANK_FILE="$sandbox/b3.bank.env" "$pos_bank" add "disk-usage" "Check disk usage" "df -h" - check_rc "pos bank add exits 0" 0 "$TR_RC" - check_contains "pos bank add confirms" "Saved: disk-usage" "$TR_OUT" + check_rc "pos system bank add exits 0" 0 "$TR_RC" + check_contains "pos system bank add confirms" "Saved: disk-usage" "$TR_OUT" # Verify the file if grep -q '^disk-usage|Check disk usage|df -h$' "$sandbox/b3.bank.env"; then - printf ' PASS pos bank add persists to bank.env\n' + printf ' PASS pos system bank add persists to bank.env\n' else - printf ' FAIL pos bank add did not persist to bank.env\n' + printf ' FAIL pos system bank add did not persist to bank.env\n' fi - # B4: pos bank add duplicate — fails + # B4: pos system bank add duplicate — fails test_run env BANK_FILE="$sandbox/b3.bank.env" "$pos_bank" add "disk-usage" "dup" "echo dup" - check_not_contains "pos bank add duplicate errors" "0" "$TR_RC" - check_contains "pos bank add duplicate message" "already exists" "$TR_OUT" + check_not_contains "pos system bank add duplicate errors" "0" "$TR_RC" + check_contains "pos system bank add duplicate message" "already exists" "$TR_OUT" - # B5: pos bank show — shows command details + # B5: pos system bank show — shows command details test_run env BANK_FILE="$sandbox/b3.bank.env" "$pos_bank" show "disk-usage" - check_rc "pos bank show exits 0" 0 "$TR_RC" - check_contains "pos bank show shows name" "Name: disk-usage" "$TR_OUT" - check_contains "pos bank show shows description" "Check disk usage" "$TR_OUT" - check_contains "pos bank show shows command" "df -h" "$TR_OUT" + check_rc "pos system bank show exits 0" 0 "$TR_RC" + check_contains "pos system bank show shows name" "Name: disk-usage" "$TR_OUT" + check_contains "pos system bank show shows description" "Check disk usage" "$TR_OUT" + check_contains "pos system bank show shows command" "df -h" "$TR_OUT" - # B6: pos bank show missing — fails + # B6: pos system bank show missing — fails test_run env BANK_FILE="$sandbox/b3.bank.env" "$pos_bank" show "nonexistent" - check_not_contains "pos bank show missing exits non-zero" "0" "$TR_RC" - check_contains "pos bank show missing message" "not found" "$TR_OUT" + check_not_contains "pos system bank show missing exits non-zero" "0" "$TR_RC" + check_contains "pos system bank show missing message" "not found" "$TR_OUT" - # B7: pos bank list after add — shows the entry + # B7: pos system bank list after add — shows the entry test_run env BANK_FILE="$sandbox/b3.bank.env" "$pos_bank" list - check_rc "pos bank list populated exits 0" 0 "$TR_RC" - check_contains "pos bank list shows header" "COMMAND BANK" "$TR_OUT" - check_contains "pos bank list shows entry" "disk-usage" "$TR_OUT" + check_rc "pos system bank list populated exits 0" 0 "$TR_RC" + check_contains "pos system bank list shows header" "COMMAND BANK" "$TR_OUT" + check_contains "pos system bank list shows entry" "disk-usage" "$TR_OUT" - # B8: pos bank remove — removes a command + # B8: pos system bank remove — removes a command test_run env BANK_FILE="$sandbox/b3.bank.env" "$pos_bank" remove "disk-usage" - check_rc "pos bank remove exits 0" 0 "$TR_RC" - check_contains "pos bank remove confirms" "Removed: disk-usage" "$TR_OUT" + check_rc "pos system bank remove exits 0" 0 "$TR_RC" + check_contains "pos system bank remove confirms" "Removed: disk-usage" "$TR_OUT" # Verify gone if grep -q '^disk-usage|' "$sandbox/b3.bank.env"; then - printf ' FAIL pos bank remove did not delete from bank.env\n' + printf ' FAIL pos system bank remove did not delete from bank.env\n' else - printf ' PASS pos bank remove deletes from bank.env\n' + printf ' PASS pos system bank remove deletes from bank.env\n' fi - # B9: pos bank remove missing — fails + # B9: pos system bank remove missing — fails : > "$sandbox/b9.bank.env" test_run env BANK_FILE="$sandbox/b9.bank.env" "$pos_bank" remove "ghost" - check_not_contains "pos bank remove missing exits non-zero" "0" "$TR_RC" - check_contains "pos bank remove missing message" "not found" "$TR_OUT" + check_not_contains "pos system bank remove missing exits non-zero" "0" "$TR_RC" + check_contains "pos system bank remove missing message" "not found" "$TR_OUT" - # B10: pos bank add with parameters — show lists them + # B10: pos system bank add with parameters — show lists them : > "$sandbox/b10.bank.env" test_run env BANK_FILE="$sandbox/b10.bank.env" "$pos_bank" add "convert" "Convert video" "ffmpeg -i {input} -q:v {quality} {output}" - check_rc "pos bank add with params exits 0" 0 "$TR_RC" + check_rc "pos system bank add with params exits 0" 0 "$TR_RC" test_run env BANK_FILE="$sandbox/b10.bank.env" "$pos_bank" show "convert" check_contains "show lists input param" "input" "$TR_OUT" check_contains "show lists quality param" "quality" "$TR_OUT" check_contains "show lists output param" "output" "$TR_OUT" - # B11: pos bank add invalid name — fails + # B11: pos system bank add invalid name — fails : > "$sandbox/b11.bank.env" test_run env BANK_FILE="$sandbox/b11.bank.env" "$pos_bank" add "1bad" "desc" "cmd" - check_not_contains "pos bank add invalid name exits non-zero" "0" "$TR_RC" - check_contains "pos bank add invalid name message" "Invalid name" "$TR_OUT" + check_not_contains "pos system bank add invalid name exits non-zero" "0" "$TR_RC" + check_contains "pos system bank add invalid name message" "Invalid name" "$TR_OUT" - # B12: pos bank run — executes a saved command (no params) + # B12: pos system bank run — executes a saved command (no params) # Negative control: the old `local name="" -a cli_params=()` declaration # crashed at line 150 before any output (rc != 0, no "Running:" line). : > "$sandbox/b12.bank.env" test_run env BANK_FILE="$sandbox/b12.bank.env" "$pos_bank" add "greet" "Greet" "echo bank-run-ok" - check_rc "pos bank add for run exits 0" 0 "$TR_RC" + check_rc "pos system bank add for run exits 0" 0 "$TR_RC" test_run env BANK_FILE="$sandbox/b12.bank.env" "$pos_bank" run "greet" - check_rc "pos bank run executes saved command" 0 "$TR_RC" - check_contains "pos bank run logs the command" "Running: echo bank-run-ok" "$TR_OUT" - check_contains "pos bank run executes output" "bank-run-ok" "$TR_OUT" + check_rc "pos system bank run executes saved command" 0 "$TR_RC" + check_contains "pos system bank run logs the command" "Running: echo bank-run-ok" "$TR_OUT" + check_contains "pos system bank run executes output" "bank-run-ok" "$TR_OUT" - # B13: pos bank run missing command — fails + # B13: pos system bank run missing command — fails test_run env BANK_FILE="$sandbox/b12.bank.env" "$pos_bank" run "ghost" - check_not_contains "pos bank run missing exits non-zero" "0" "$TR_RC" - check_contains "pos bank run missing message" "Command not found" "$TR_OUT" + check_not_contains "pos system bank run missing exits non-zero" "0" "$TR_RC" + check_contains "pos system bank run missing message" "Command not found" "$TR_OUT" - # B14: pos bank run with params — CLI key=val substitution, no interactive prompt + # B14: pos system bank run with params — CLI key=val substitution, no interactive prompt : > "$sandbox/b14.bank.env" test_run env BANK_FILE="$sandbox/b14.bank.env" "$pos_bank" add "echo-param" "Echo param" "echo hi {who}" - check_rc "pos bank add param exits 0" 0 "$TR_RC" + check_rc "pos system bank add param exits 0" 0 "$TR_RC" test_run env BANK_FILE="$sandbox/b14.bank.env" "$pos_bank" run "echo-param" "who=there" - check_rc "pos bank run with params exits 0" 0 "$TR_RC" - check_contains "pos bank run substitutes param" 'Running: echo hi "there"' "$TR_OUT" - check_contains "pos bank run executes substituted command" "hi there" "$TR_OUT" + check_rc "pos system bank run with params exits 0" 0 "$TR_RC" + check_contains "pos system bank run substitutes param" 'Running: echo hi "there"' "$TR_OUT" + check_contains "pos system bank run executes substituted command" "hi there" "$TR_OUT" - # B15: pos bank add multiline + show — cmd_show retrieves the FULL script via arrays + # B15: pos system bank add multiline + show — cmd_show retrieves the FULL script via arrays # Negative control: bank_get + cut -f3 truncated the command at the first newline. # (Brace-free script: {param} template detection would prompt on run in a non-TTY.) : > "$sandbox/b15.bank.env" @@ -451,17 +451,17 @@ done SCRIPT )" test_run env BANK_FILE="$sandbox/b15.bank.env" "$pos_bank" add "ml-demo" "Multiline demo" "$ml_script" - check_rc "pos bank add multiline exits 0" 0 "$TR_RC" + check_rc "pos system bank add multiline exits 0" 0 "$TR_RC" test_run env BANK_FILE="$sandbox/b15.bank.env" "$pos_bank" show "ml-demo" - check_rc "pos bank show multiline exits 0" 0 "$TR_RC" - check_contains "pos bank show prints script shebang" '#!/usr/bin/env bash' "$TR_OUT" - check_contains "pos bank show prints loop line" 'while [ "$n" -lt 2 ]; do' "$TR_OUT" - check_contains "pos bank show prints arithmetic line" 'n=$((n + 1))' "$TR_OUT" - check_contains "pos bank show prints substitution echo" 'echo "round $n: $(printf' "$TR_OUT" + check_rc "pos system bank show multiline exits 0" 0 "$TR_RC" + check_contains "pos system bank show prints script shebang" '#!/usr/bin/env bash' "$TR_OUT" + check_contains "pos system bank show prints loop line" 'while [ "$n" -lt 2 ]; do' "$TR_OUT" + check_contains "pos system bank show prints arithmetic line" 'n=$((n + 1))' "$TR_OUT" + check_contains "pos system bank show prints substitution echo" 'echo "round $n: $(printf' "$TR_OUT" - # B16: pos bank run multiline — executes the WHOLE script via eval of the full command + # B16: pos system bank run multiline — executes the WHOLE script via eval of the full command test_run env BANK_FILE="$sandbox/b15.bank.env" "$pos_bank" run "ml-demo" - check_rc "pos bank run multiline exits 0" 0 "$TR_RC" - check_contains "pos bank run multiline output line 1" "round 1: ok" "$TR_OUT" - check_contains "pos bank run multiline output line 2" "round 2: ok" "$TR_OUT" + check_rc "pos system bank run multiline exits 0" 0 "$TR_RC" + check_contains "pos system bank run multiline output line 1" "round 1: ok" "$TR_OUT" + check_contains "pos system bank run multiline output line 2" "round 2: ok" "$TR_OUT" }