cfg_read_secret is invoked via $() inside _cfg_edit_one, so the echo that
advanced the cursor after hidden input landed on the capture pipe instead of
the terminal — every secret value stored with pos config on a real TTY got a
leading newline (e.g. AI_GEMINI_API_KEY="\n<key>"), which neither cfg_value
nor the ai/telegram load_config parsers could read back: the menu showed
'(not set)' and pos ai gemini kept demanding a key.
Fix: send that newline to stderr (>&2). Defense in depth: cfg_write and
write_config_key now strip CR and truncate multi-line pastes, and the readers
(cfg_value, ai + telegram load_config) strip CR. Reproduced and verified on a
pseudo-TTY; piped-stdin tests never caught it because non-TTY input skips the
stty/echo path.
- Drop sender 'config'/'config set' subcommand — redundant with 'pos config
telegram' (same POS_CONFIG registry: masked token input/display, chat-id
validation, chmod 600); sender/listener error hints now point there.
- Listener: load_map now uses a \x1f delimiter, so mapped /commands
containing '|' are no longer truncated at the first pipe.
- entertainment send: forward extra plugin args (previously dropped); pass
'--' before the message so leading '-' plugin output isn't parsed as an
option.
- write_config_key/cfg_write: replace unescaped sed writes with grep-v+append
so values with &, |, \ round-trip safely.
- sync_systemd: daemon-reload after removing timer units.
- config-ui: 'digits' validation accepts negative group/supergroup chat ids.
Under 'set -u', 'local field=$1 key flags desc rest example' leaves the
unassigned locals UNSET rather than empty, so any POS_CONFIG key without
an '::example' hint (e.g. the whole system scope) hit 'example: unbound
variable' and aborted the scope listing. Initialize all locals explicitly.
POS_CONFIG fields gain an optional 'desc::example' value-format hint shown
in the editor ('e.g. weather,5m joke,10m' under ENABLED). Harden
entertainment-lib's plugin_marker/plugin_keys grep|sed pipelines with
'|| true': under set -o pipefail a grep miss exited 1 and set -e aborted
config_keys mid-scan, so installed layouts (/usr/local/bin mixes libs and
plugins) silently lost all plugin keys in pos config.
Add bin/pos-config + lib/config-ui.sh: reads the '# POS_CONFIG:' headers
across tools into a unified runtime config (~/.config/linux_post_install/
<scope>.env, chmod 600). Secret masking with show/hide, digits:/num:/url:
validation, '-' clears, blank keeps; '*plugins' expands entertainment
plugin vars from entertainment-lib.sh. pos config with no scope opens a
picker (scope list when not a TTY); completion offers scopes from the
registry at runtime.
gen-docs.sh now handles category-less tools (pos-config) instead of
emitting pos-config-config; fix set -e/pipefail abort that truncated the
header registry mid-read. install.sh ships config-ui.sh.
The --send flag duplicated the send subcommand — completion suggested both.
Now 'pos communication telegram <TAB>' completes to just 'sender' and
'listener'. send is the single canonical action.
- git mv bin/pos-communication-telegram bin/pos-communication-telegram-sender
- remove --send branch + POS_FLAGS entry; add --markdown to completion
- lib/notify.sh: notify_sender_name() maps platform telegram -> telegram-sender
(notify_send otherwise looks for bin/pos-communication-telegram)
- pos-system-health --send 'sent:' check + pos-entertainment-send use the new
name and the send subcommand
- completion: keys with nested tools but no direct tool complete to the group
suffixes (sender/listener); --type/--parse-mode value completion moved to
the new word positions
- docs updated (POS.md, AGENT_Context, DEV.md, SCRIPTS.md, notify.env,
HOWTO.md, postinstall.sh); removed phantom webhook/log/broadcast subcommands
from howto/communication.md
- spawn() now respects DRY_RUN, install.sh exports it to child phases, and
postinstall.sh wraps every user-home mutation in run() — '--dry-run' no
longer runs apt/install/clone or edits dotfiles for real
- gen-docs.sh chmods regenerated files to 644 (mktemp mv left them at 0600)
- make check now syntax-checks apps/, entertainment/, features/, templates/
- .gitignore protects config/authorized_keys + config/rclone.conf; drop the
tracked empty authorized_keys and the stray 6 MB session file
- pos-system-health --send prints 'sent:' only when a platform sender exists,
otherwise warns on stderr (notify_send is silent-fail)
- standardize sourced libs (no shebang); refresh AGENT_Context/DEV/APPS/SCRIPTS
doc drift: notify.sh in lib lists, pos-health systemd units, tsui, scripts/,
INTERACTIVE_CMDS list, entertainment scheduler (systemd timers only)
- lib/notify.sh: route notify_send to every platform in NOTIFY_PLATFORM
(notify.env, default telegram; comma-separated = send to all). New
platforms need only a bin/pos-communication-<p> sender implementing
'send <value> [--markdown]' (Matrix/Synapse ready)
- pos-communication-telegram: add --markdown as alias for --parse-mode
markdown to match the sender contract
- lib/common.sh: load_system_env() — shared ~/.config/linux_post_install/
system.env for pos-system-* tools (env exported > file > default)
- pos-system-health/backup: load system.env and show effective dynamic
values (NOTIFY_PLATFORM, HEALTH_BACKUP_MAX_AGE_DAYS, BACKUP_SERVICE_ROOTS)
in --help
- config/system.env + config/notify.env templates copied by postinstall
- systemd/pos-health.service: EnvironmentFile for both configs so the
daily digest honors them
- Move autostart.sh from bin/ to features/ so re-installs never reset it
- Add lib/flags.sh (flag_set/clear/is_set/value/list/status) + flag-reader/set/clear CLIs
- install.sh --feature copies features/* to /usr/local/bin with overwrite prompt, sets flags
- postinstall.sh enables autostart.service only when the autostart flag is set
- Document features & flags in README, DEV.md, AGENT_Context_Project.md