Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11b4a679a8 | |||
| e23d57e551 | |||
| c5d501ad25 |
@@ -42,6 +42,8 @@ summary (newest last).
|
||||
|
||||
## Done
|
||||
|
||||
- **`pos bank` command bank** — feature (41efc91) + lib registration fix (73d15a2) + main-menu visibility & `run` crash fix. Done 2026-09-12.
|
||||
|
||||
- **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** — 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.
|
||||
|
||||
@@ -628,14 +628,14 @@ 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` | 175 | Shared Command Bank storage helpers for `pos bank` (`bank_load`/`bank_save`/`bank_add`/`bank_remove`/`bank_update`, `{param}` template substitution; store `~/.config/linux_post_install/bank.env`) — sourced by `pos-bank` |
|
||||
| `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` |
|
||||
| `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 |
|
||||
| `features/autostart.sh` | 50 | Boot-time feature (moved from `bin/`, flag-gated service) |
|
||||
| `features/usb-automount.sh` | 138 | USB automount feature (udev rule + flag-gated service) |
|
||||
<!-- GEN:START filetable -->
|
||||
| `bin/pos` | 302 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos` | 310 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos-ai-alias` | 760 | manage AI agent aliases |
|
||||
| `bin/pos-ai-gemini` | 7 | Forward to pos ai --provider gemini (backward compat) |
|
||||
| `bin/pos-ai-hf` | 993 | Download AI models from Hugging Face (search, download, manage) |
|
||||
@@ -686,10 +686,10 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `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` | 313 | Persistent command bank for saving and running shell commands |
|
||||
| `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` | 318 | Dynamic bash completion |
|
||||
| `completions/pos.bash` | 331 | Dynamic bash completion |
|
||||
<!-- GEN:END filetable -->
|
||||
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@ _pos_commands() {
|
||||
# ── Category list (sorted, filename-derived) ─────────────────────
|
||||
# Nested sub-tools (pos-<cat>-<a>-<b> where pos-<cat>-<a> 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
|
||||
# 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() {
|
||||
local -A cats=()
|
||||
local cmd cat f
|
||||
@@ -35,7 +38,10 @@ _pos_category_list() {
|
||||
[ -x "$f" ] || continue
|
||||
cmd="${f##*/pos-}"
|
||||
cat="${cmd%%-*}"
|
||||
[ "$cat" = "$cmd" ] && continue
|
||||
if [ "$cat" = "$cmd" ]; then
|
||||
cats["$cmd"]="${cats[$cmd]:-}"
|
||||
continue
|
||||
fi
|
||||
cats["$cat"]+="${cmd#*-} "
|
||||
done
|
||||
local c subs joined s s2 top=() is_nested
|
||||
@@ -208,6 +214,8 @@ EXAMPLES
|
||||
|
||||
pos tree Show the command tree (categories + subcommands)
|
||||
|
||||
pos bank run <name> 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
|
||||
pos docker vbox enter lab1 Shell into a Docker VM
|
||||
|
||||
+14
-16
@@ -114,12 +114,11 @@ cmd_show() {
|
||||
[ -n "${1:-}" ] || err "Usage: pos bank show <name>"
|
||||
local name="$1"
|
||||
bank_load
|
||||
local entry
|
||||
entry="$(bank_get "$name")" || err "Command not found: $name"
|
||||
bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
|
||||
local cmd_name cmd_desc cmd_cmd
|
||||
cmd_name="$(printf '%s' "$entry" | cut -f1)"
|
||||
cmd_desc="$(printf '%s' "$entry" | cut -f2)"
|
||||
cmd_cmd="$(printf '%s' "$entry" | cut -f3)"
|
||||
cmd_name="${BANK_NAMES[$BANK_IDX]}"
|
||||
cmd_desc="${BANK_DESCS[$BANK_IDX]}"
|
||||
cmd_cmd="${BANK_CMDS[$BANK_IDX]}"
|
||||
echo
|
||||
echo "Name: $cmd_name"
|
||||
echo
|
||||
@@ -147,7 +146,8 @@ cmd_show() {
|
||||
|
||||
# ── cmd_run ──────────────────────────────────────────────────────
|
||||
cmd_run() {
|
||||
local name="" -a cli_params=()
|
||||
local name=""
|
||||
local -a cli_params=()
|
||||
if [ $# -eq 0 ]; then
|
||||
name="$(_pick_command)" || return 0
|
||||
else
|
||||
@@ -161,12 +161,11 @@ cmd_run() {
|
||||
done
|
||||
fi
|
||||
bank_load
|
||||
local entry
|
||||
entry="$(bank_get "$name")" || err "Command not found: $name"
|
||||
bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
|
||||
local cmd_name cmd_desc cmd_cmd
|
||||
cmd_name="$(printf '%s' "$entry" | cut -f1)"
|
||||
cmd_desc="$(printf '%s' "$entry" | cut -f2)"
|
||||
cmd_cmd="$(printf '%s' "$entry" | cut -f3)"
|
||||
cmd_name="${BANK_NAMES[$BANK_IDX]}"
|
||||
cmd_desc="${BANK_DESCS[$BANK_IDX]}"
|
||||
cmd_cmd="${BANK_CMDS[$BANK_IDX]}"
|
||||
# Detect parameters
|
||||
local params_str
|
||||
params_str="$(_extract_params "$cmd_cmd")"
|
||||
@@ -217,12 +216,11 @@ cmd_edit() {
|
||||
name="$(_pick_command)" || return 0
|
||||
fi
|
||||
bank_load
|
||||
local entry
|
||||
entry="$(bank_get "$name")" || err "Command not found: $name"
|
||||
bank_find "$name" >/dev/null 2>&1 || err "Command not found: $name"
|
||||
local old_name old_desc old_cmd
|
||||
old_name="$(printf '%s' "$entry" | cut -f1)"
|
||||
old_desc="$(printf '%s' "$entry" | cut -f2)"
|
||||
old_cmd="$(printf '%s' "$entry" | cut -f3)"
|
||||
old_name="${BANK_NAMES[$BANK_IDX]}"
|
||||
old_desc="${BANK_DESCS[$BANK_IDX]}"
|
||||
old_cmd="${BANK_CMDS[$BANK_IDX]}"
|
||||
echo
|
||||
log "Editing: $old_name"
|
||||
echo
|
||||
|
||||
+15
-2
@@ -115,7 +115,14 @@ _pos() {
|
||||
|
||||
# ── Helpers ────────────────────────────────────────────────
|
||||
_pos_complete_categories() {
|
||||
COMPREPLY=($(compgen -W "${!cat_cmds[*]} config" -- "$cur"))
|
||||
local solos=()
|
||||
for cmd in "${all_cmds[@]}"; do
|
||||
local cat_c="${cmd%%-*}"
|
||||
if [ "$cat_c" = "$cmd" ] && [ -z "${cat_cmds[$cmd]:-}" ]; then
|
||||
solos+=("$cmd")
|
||||
fi
|
||||
done
|
||||
COMPREPLY=($(compgen -W "${!cat_cmds[*]} ${solos[*]}" -- "$cur"))
|
||||
}
|
||||
|
||||
_pos_complete_subcats() {
|
||||
@@ -231,7 +238,13 @@ _pos() {
|
||||
3)
|
||||
case "${words[1]}" in
|
||||
config) _pos_config_scopes ;;
|
||||
*) _pos_complete_subcats ;;
|
||||
*)
|
||||
if [[ "${words[1]}" == *-* ]]; then
|
||||
_pos_complete_subcats
|
||||
else
|
||||
_pos_complete_tool "${words[1]}"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
4)
|
||||
|
||||
+12
-4
@@ -4,6 +4,8 @@
|
||||
#
|
||||
# Storage: ~/.config/linux_post_install/bank.env
|
||||
# Format: name|description|command
|
||||
# v2: commands with real newlines are saved with \\ (backslash) and \n
|
||||
# (newline) escapes; files written by v1 have no marker and load raw.
|
||||
#
|
||||
# Contracts:
|
||||
# * Defines ONLY bank_* functions — sourcing never clobbers a tool's helpers.
|
||||
@@ -20,12 +22,13 @@ bank_load() {
|
||||
BANK_NAMES=(); BANK_DESCS=(); BANK_CMDS=()
|
||||
[ -f "$BANK_FILE" ] || return 0
|
||||
|
||||
local line name desc cmd
|
||||
local line name desc cmd v2=0
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && { [[ "$line" == *"BANK_VERSION: 2"* ]] && v2=1; continue; }
|
||||
[[ -z "${line// /}" ]] && continue
|
||||
IFS='|' read -r name desc cmd <<< "$line"
|
||||
[[ -z "$name" ]] && continue
|
||||
[ "$v2" -eq 1 ] && cmd="$(printf '%b' "$cmd")"
|
||||
BANK_NAMES+=("$name")
|
||||
BANK_DESCS+=("${desc:-}")
|
||||
BANK_CMDS+=("${cmd:-}")
|
||||
@@ -42,10 +45,15 @@ bank_save() {
|
||||
tmp="$(mktemp "${dir}/.bank.XXXXXX")"
|
||||
{
|
||||
printf '%s\n' "# Command Bank — managed by pos bank (do not hand-edit)"
|
||||
printf '%s\n' "# Format: name|description|command"
|
||||
printf '%s\n' "# Format: name|description|command (\\n = escaped newline in command)"
|
||||
printf '%s\n' "# BANK_VERSION: 2"
|
||||
local i
|
||||
for ((i = 0; i < ${#BANK_NAMES[@]}; i++)); do
|
||||
printf '%s|%s|%s\n' "${BANK_NAMES[$i]}" "${BANK_DESCS[$i]}" "${BANK_CMDS[$i]}"
|
||||
local cmd="${BANK_CMDS[$i]}"
|
||||
# Escape: every backslash → \\, every real newline → \n (keeps one record per physical line)
|
||||
cmd="${cmd//\\/\\\\}"
|
||||
cmd="${cmd//$'\n'/\\n}"
|
||||
printf '%s|%s|%s\n' "${BANK_NAMES[$i]}" "${BANK_DESCS[$i]}" "$cmd"
|
||||
done
|
||||
} > "$tmp"
|
||||
chmod 600 "$tmp"
|
||||
|
||||
+1
-1
@@ -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) + pos-bank CLI integration (help, list, add, show, remove, params, invalid name) |
|
||||
| `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) |
|
||||
+130
@@ -250,6 +250,83 @@ BANK
|
||||
) && printf ' PASS bank_load skips comments and blank lines\n' \
|
||||
|| printf ' FAIL bank_load skips comments and blank lines\n'
|
||||
|
||||
# A19: multiline command storage round-trip — escapes \n, single record line
|
||||
(
|
||||
BANK_FILE="$sandbox/a19.bank.env"
|
||||
: > "$BANK_FILE"
|
||||
source "$helper"
|
||||
script="$(cat <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
for tag in one two; do
|
||||
out="$(printf '%s' "$tag" | tr 'a-z' 'A-Z')"
|
||||
echo "tag=${tag} out=${out} done"
|
||||
done
|
||||
SCRIPT
|
||||
)"
|
||||
bank_add "ml" "Multiline demo" "$script"
|
||||
bank_load
|
||||
[ "${#BANK_NAMES[@]}" -eq 1 ] || { echo "FAIL A19: expected 1 entry, got ${#BANK_NAMES[@]}"; exit 1; }
|
||||
[ "${BANK_CMDS[0]}" = "$script" ] || { echo "FAIL A19: command bytes differ"; exit 1; }
|
||||
rec_lines="$(grep -c '^ml|' "$BANK_FILE" || true)"
|
||||
[ "$rec_lines" -eq 1 ] || { echo "FAIL A19: expected 1 record line, got $rec_lines"; exit 1; }
|
||||
[ "$(wc -l < "$BANK_FILE")" -eq 4 ] || { echo "FAIL A19: file has bogus physical lines"; exit 1; }
|
||||
grep -q '\\n' "$BANK_FILE" || { echo "FAIL A19: missing \\n escape in file"; exit 1; }
|
||||
echo "PASS A19: multiline round-trip"
|
||||
) && printf ' PASS multiline storage round-trip (one record line, \\n escapes)\n' \
|
||||
|| printf ' FAIL multiline storage round-trip (one record line, \\n escapes)\n'
|
||||
|
||||
# A20: literal \n (backslash-n) in command round-trips literally — NOT a real newline
|
||||
(
|
||||
BANK_FILE="$sandbox/a20.bank.env"
|
||||
: > "$BANK_FILE"
|
||||
source "$helper"
|
||||
esc="$(printf '%s' "printf 'a\\nb'")"
|
||||
bank_add "esc" "Escapes" "$esc"
|
||||
bank_load
|
||||
[ "${BANK_CMDS[0]}" = "$esc" ] || { echo "FAIL A20: literal \\n not preserved [${BANK_CMDS[0]}]"; exit 1; }
|
||||
case "${BANK_CMDS[0]}" in
|
||||
*$'\n'*) echo "FAIL A20: decoded literal \\n into a real newline"; exit 1 ;;
|
||||
esac
|
||||
echo "PASS A20: literal \\n round-trip"
|
||||
) && printf ' PASS literal \\n command round-trips literally\n' \
|
||||
|| printf ' FAIL literal \\n command round-trips literally\n'
|
||||
|
||||
# A21: old-format file (no BANK_VERSION marker) keeps raw backslashes — no %b decode
|
||||
(
|
||||
BANK_FILE="$sandbox/a21.bank.env"
|
||||
cat > "$BANK_FILE" <<'BANK'
|
||||
# Command Bank — managed by pos bank (do not hand-edit)
|
||||
# Format: name|description|command
|
||||
raw|Raw echo|echo 'a\b'
|
||||
BANK
|
||||
source "$helper"
|
||||
bank_load
|
||||
expected="$(printf '%s' "echo 'a\\b'")"
|
||||
[ "${BANK_CMDS[0]}" = "$expected" ] || { echo "FAIL A21: raw backslash changed [${BANK_CMDS[0]}]"; exit 1; }
|
||||
echo "PASS A21: old-format raw backslash"
|
||||
) && printf ' PASS old-format file keeps raw backslash (no decode)\n' \
|
||||
|| printf ' FAIL old-format file keeps raw backslash (no decode)\n'
|
||||
|
||||
# A22: old-format entry survives a re-save byte-identically (escape + decode round-trip)
|
||||
(
|
||||
BANK_FILE="$sandbox/a22.bank.env"
|
||||
cat > "$BANK_FILE" <<'BANK'
|
||||
# Command Bank — managed by pos 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
|
||||
source "$helper"
|
||||
bank_load
|
||||
orig="${BANK_CMDS[0]}"
|
||||
bank_add "new-cmd" "New entry" "echo ok"
|
||||
bank_load
|
||||
[ "${#BANK_NAMES[@]}" -eq 2 ] || { echo "FAIL A22: expected 2 entries, got ${#BANK_NAMES[@]}"; exit 1; }
|
||||
[ "${BANK_CMDS[0]}" = "$orig" ] || { echo "FAIL A22: old entry changed after re-save"; exit 1; }
|
||||
[ "${BANK_CMDS[1]}" = "echo ok" ] || { echo "FAIL A22: new entry missing"; exit 1; }
|
||||
echo "PASS A22: old-format re-save round-trip"
|
||||
) && printf ' PASS old-format entry round-trips through re-save\n' \
|
||||
|| printf ' FAIL old-format entry round-trips through re-save\n'
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Part B: pos-bank CLI integration tests
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
@@ -334,4 +411,57 @@ BANK
|
||||
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"
|
||||
|
||||
# B12: pos 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"
|
||||
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"
|
||||
|
||||
# B13: pos 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"
|
||||
|
||||
# B14: pos 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"
|
||||
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"
|
||||
|
||||
# B15: pos 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"
|
||||
ml_script="$(cat <<'SCRIPT'
|
||||
#!/usr/bin/env bash
|
||||
n=0
|
||||
while [ "$n" -lt 2 ]; do
|
||||
n=$((n + 1))
|
||||
echo "round $n: $(printf 'ok')"
|
||||
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"
|
||||
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"
|
||||
|
||||
# B16: pos 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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user