# Architecture: `pos ai alias` — AI Agent Alias Manager **Date:** 2026-08-26 **Author:** Architect (big-pickle) **Status:** DECISION_READY --- ## TL;DR | Decision | Choice | |----------|--------| | Storage | `~/.config/linux_post_install/ai-aliases.env` (pipe-delimited structured data) | | Shell aliases | `~/.config/linux_post_install/ai-aliases.sh` (generated, never hand-edited) | | Tool | `bin/pos-ai-alias` — new `ai` category tool | | No POS_CONFIG scope | Alias management is CRUD, not env-key editing; `pos config` is not involved | | Source of truth | `.env` file; `.sh` file is regenerated on every write | | .bashrc integration | One conditional `source` line, added by postinstall.sh | | INSTALL_CHANGES | `postinstall.sh` adds `.bashrc` source line; `bin/pos` adds `ai-alias` to INTERACTIVE_CMDS | **Open items:** - None — all decisions are evidence-backed from project conventions. --- ## Decision 1: Data Format **Problem:** Store named AI aliases (provider, session name, system prompt) durably, with safe special-character handling and trivial parsing. ### Options Considered **Option A: Shell alias format (one-liner shell aliases)** ``` alias devbot='pos ai gemini ask --session devbot --system "You are a dev assistant"' ``` - Architecture: Store raw shell alias lines; `.bashrc` sources the file directly. - Advantages: No generation step; shell sources it natively. - Costs: Parsing aliases back into components (for edit/list) requires fragile quote-aware shell parsing; single quotes inside system prompts break the syntax. - Risks: Prompt containing `'` corrupts the file. Edit must read then reconstruct — fragile. **Option B: Pipe-delimited structured data + generated .sh** ``` devbot|gemini|devbot|You are a dev assistant ``` - Architecture: `.env` file is the source of truth (pipe-delimited fields). A separate `.sh` file is regenerated from it on every write. `.bashrc` sources the `.sh` file. - Advantages: Parsing is trivial (`IFS='|'`); single-quote escaping is handled at generation time; data is safe to `grep`/`sort`/`awk`. - Costs: One extra file (`.sh`); a `_regen_aliases()` helper function. - Risks: Generation must escape correctly — but this is a single, testable function. **Option C: Individual env files per alias** ``` ~/.config/linux_post_install/ai-aliases/devbot.env ``` - Architecture: One file per alias; load all at shell startup. - Advantages: No parsing of multi-entry files. - Costs: Directory management; glob at shell startup; harder to list all; no atomic operations. ### Decision: Option B Pipe-delimited structured data is the cleanest separation. The `.env` file is the source of truth. The `.sh` file is a generated artifact. This matches the project's own pattern of "generated code between GEN markers" — except here the generator lives inside the tool itself, not `make gen`. **Rationale:** The project already has a pattern of generated files (e.g., `GEN:START`/`GEN:END` blocks, `completions/pos.bash`). The tool owns its own generation. Parsing shell aliases is fragile and error-prone; pipe-delimited data is trivially safe. ### Data Format Specification ``` # ~/.config/linux_post_install/ai-aliases.env # Managed by: pos ai alias (do not hand-edit) # Format: alias_name|provider|session_name|system_prompt # Pipe characters in system_prompt are not supported. # # agent_name|provider|session_name|system_prompt devbot|gemini|devbot|You are a Linux dev assistant. Reply with commands only. code|openrouter|codereview|You are a code reviewer. Be concise. ``` **Field constraints:** | Field | Rules | |-------|-------| | `alias_name` | Shell-valid identifier: `[a-zA-Z][a-zA-Z0-9_-]*` | | `provider` | Must match an installed provider: `gemini`, `openrouter`, etc. | | `session_name` | Defaults to the alias name if empty; `[a-zA-Z0-9_-]+` | | `system_prompt` | Free text; no `\|` (pipe) characters; may be empty (uses built-in prompt) | **Header:** Two comment lines at the top (file description + format) are auto-maintained by the tool. **Empty session_name convention:** When session_name is empty (field is blank between pipes), the alias uses the alias name as the session name. This avoids redundant repetition for the common case. --- ## Decision 2: File Layout ### New files | File | Purpose | |------|---------| | `bin/pos-ai-alias` | New CLI tool (100755) | ### Modified files | File | Change | |------|--------| | `bin/pos` | Add `ai-alias` to `INTERACTIVE_CMDS` list | | `postinstall.sh` | Add `.bashrc` source line for `ai-aliases.sh` (conditional, no-clobber) | ### Runtime files (user config, NOT in repo) | File | Purpose | Mutability | |------|---------|------------| | `~/.config/linux_post_install/ai-aliases.env` | Alias data (source of truth) | Created/modified by tool | | `~/.config/linux_post_install/ai-aliases.sh` | Generated shell aliases | Regenerated on every write | ### Files NOT modified | File | Why not | |------|---------| | `.gitignore` | `~/.config/linux_post_install/` is a user directory, not in the repo. No new repo files to ignore. | | `config/` | No template file needed — the env file is user-created on first use. | | `lib/` | No new library. Tool sources `common.sh` + `menu-lib.sh` from existing libs. | --- ## Decision 3: Tool Interface ### POS Header ```bash # POS: ai alias — Create/edit/remove named AI agent aliases # POS_SUBCMDS: create edit remove list show ``` No `POS_FLAGS:` — this is a subcommand-based tool, not a flag-based tool. ### CLI Interface ``` pos ai alias [subcommand] [args] Subcommands: (no args) Interactive menu (create/edit/remove/list) create Create a new alias (interactive prompts) create Create with given name (interactive prompts for rest) edit Pick an alias to edit (interactive) edit Edit a specific alias remove Pick an alias to remove (interactive, with confirmation) remove Remove a specific alias (with confirmation) list List all aliases (non-interactive, machine-readable) show Show one alias's details Options: -h|--help Show this help. Examples: pos ai alias # interactive menu pos ai alias list # show all aliases pos ai alias create # interactive create pos ai alias create mybot # create 'mybot' alias pos ai alias edit mybot # edit the 'mybot' alias pos ai alias remove mybot # remove 'mybot' (with confirm) pos ai alias show mybot # show alias details ``` ### Subcommand Details **`pos ai alias` (no args):** Interactive menu using `menu_run` from `lib/menu-lib.sh`. Options: 1. Create new alias 2. Edit existing alias 3. Remove alias 4. List aliases **`pos ai alias list`:** Non-interactive table output: ``` Aliases (3): mybot gemini mybot You are a helpful assistant code openrouter code You are a code reviewer dev gemini devbot Dev assistant ``` Format: `%-12s %-12s %-12s %s` (name, provider, session, prompt-truncated-to-60). **`pos ai alias show `:** Full details including the resolved shell command. --- ## Decision 4: POS_CONFIG **Decision: NO POS_CONFIG scope.** **Rationale:** The `pos config` / `POS_CONFIG` system is designed for simple `KEY=VALUE` env files (like `ai.env`, `telegram.env`, `entertainment.env`). Aliases are structured, multi-field records, not key-value pairs. The `cfg_ui()` pattern from `lib/config-ui.sh` doesn't apply here — it renders a numbered menu of KEY=VALUE pairs, not CRUD operations on named records. The alias tool is self-contained with its own interactive menus. It does not participate in `pos config`. --- ## Decision 5: Shell Alias Generation ### The `_regen_aliases()` function This function reads `ai-aliases.env` and writes `ai-aliases.sh`: ```bash _regen_aliases() { local env_file="$1" sh_file="$2" tmp tmp="$(mktemp)" printf '#!/usr/bin/env bash\n# Auto-generated by pos ai alias — do not hand-edit.\n# Source: %s\n\n' "$env_file" >"$tmp" if [ -f "$env_file" ]; then while IFS='|' read -r name provider session prompt _rest; do # Skip comments and empty lines [[ "$name" =~ ^[[:space:]]*# ]] && continue [[ -z "$name" ]] && continue # Validate alias name [[ "$name" =~ ^[a-zA-Z][a-zA-Z0-9_-]*$ ]] || continue # Session defaults to alias name if empty [ -z "$session" ] && session="$name" # Escape single quotes in the system prompt for shell-safe embedding 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) fi mv "$tmp" "$sh_file" chmod 644 "$sh_file" } ``` **Single-quote escaping:** `${prompt//\'/\'\\\'\'}` — bash `parameter expansion` replaces every `'` with `'\''` (close-quote, escaped-quote, open-quote). This is the standard and safe pattern for embedding arbitrary strings in single-quoted shell contexts. **Edge case — empty prompt:** When the system prompt is empty, the `--system` flag is omitted entirely, letting `pos ai` use its built-in default prompt. ### Syntax validation before commit After generating the `.sh` file, run `bash -n` to verify syntax: ```bash if ! bash -n "$sh_file" 2>/dev/null; then warn "Generated alias file has syntax errors — keeping previous version" rm -f "$tmp" return 1 fi ``` ### Shell integration in .bashrc ```bash # AI aliases (managed by pos ai alias) [ -f ~/.config/linux_post_install/ai-aliases.sh ] && source ~/.config/linux_post_install/ai-aliases.sh ``` This line is added by `postinstall.sh` with the standard no-clobber grep check. --- ## Decision 6: Interactive Flow ### Main Menu (`pos ai alias` — no args) Uses `menu_run` from `lib/menu-lib.sh`: ``` ════════════════════════════════════════════ AI Agent Aliases ════════════════════════════════════════════ 1) Create new alias 2) Edit existing alias 3) Remove alias 4) List aliases 0) Exit ---------------------------------------- Choose: ``` ### Create Flow 1. Prompt for alias name: `menu_ask_value "Alias name" ""` — validate format (`[a-zA-Z][a-zA-Z0-9_-]*`) 2. Check for duplicate name → warn and re-prompt if taken 3. Prompt for provider: show available providers (read from `$PROVIDER_DIR/*.sh`), default `gemini` 4. Prompt for session name: default = alias name 5. Prompt for system prompt: default = empty (uses built-in) 6. Confirm: `confirm "Create alias ''?" y` 7. Write to `.env`, regenerate `.sh`, log success ### Edit Flow 1. List existing aliases (name + provider + first-40-chars of prompt) 2. Pick one (if no arg given): `menu_pick "Pick alias" "${names[@]}"` 3. Show current values 4. For each field, prompt with current value as default (Enter = keep) 5. Confirm changes 6. Rewrite `.env` entry, regenerate `.sh` ### Remove Flow 1. Pick alias: `menu_pick` or named 2. Show alias details 3. `confirm "Remove alias ''? This cannot be undone." n` (default = no) 4. Remove from `.env`, regenerate `.sh` ### List Flow (non-interactive) Prints formatted table to stdout. Provider column left-aligned, name left-aligned, prompt truncated to 60 chars with `...`. ### Show Flow (non-interactive) Full details + resolved command: ``` Alias: mybot Provider: gemini Session: mybot Prompt: You are a helpful assistant Command: pos ai gemini ask --session mybot --system 'You are a helpful assistant' ``` --- ## Decision 7: Edge Cases and Error Handling | Edge Case | Handling | |-----------|----------| | **Duplicate alias name on create** | `warn "Alias '$name' already exists — use 'pos ai alias edit $name' instead"`; re-prompt | | **Empty alias name** | `err "Alias name cannot be empty"` | | **Invalid alias name** (contains spaces, starts with digit) | `err "Invalid alias name '$name' — use letters, digits, hyphens, underscores"` | | **Invalid provider** | `err "Unknown provider '$p' — available: $(ls ...)"` — uses same provider discovery as `pos-ai` | | **Empty system prompt** | Allowed — omit `--system` flag; `pos ai` uses its built-in default prompt | | **Very long system prompt** | Truncate display in `list` output (60 chars + `...`); full value preserved in `.env` and `.sh`. Warn if > 500 chars during creation. | | **Pipe character in system prompt** | Rejected on input: `err "System prompt must not contain '\|' characters"` | | **System prompt with single quotes** | Handled by `_regen_aliases()` escaping: `'` -> `'\''` in the generated alias line | | **Editing an alias "in use"** | No lock/detection needed. The user edits the `.env`; on next shell startup (or `source ~/.bashrc`), aliases update. No runtime state conflict. | | **File doesn't exist yet** | First create auto-creates both `.env` and `.sh` | | **Corrupted/invalid .env line** | Skipped by `_regen_aliases()` (name validation regex) | | **Concurrent edits** | Not a concern — personal single-user tool. Last write wins. | | **Missing pos-ai dependency** | Tool doesn't require `pos-ai` at runtime — it only writes config. No dep guard needed. | --- ## Decision 8: Security Considerations ### System prompt injection System prompts are user-authored text that becomes a shell argument. Risks: - **Shell injection via alias execution:** The prompt is single-quoted in the alias, so shell metacharacters (`$`, backtick, `!`) are literal — safe. - **`pos ai` prompt injection:** This is a user's own prompt for their own AI. No trust boundary crossing. - **File permissions:** `ai-aliases.env` gets `chmod 600` (user-only read; consistent with other config files). `ai-aliases.sh` gets `chmod 644` (needed by bash `source`). ### Escaping correctness The single-quote escaping `${prompt//\'/\'\\\'\'}` is the only place where correctness matters critically. If it fails, the generated alias has a syntax error and `source` will report it. Mitigation: - The tool runs `bash -n` on the generated `.sh` file before committing it. - If syntax check fails, warn and skip the regeneration (keep the old `.sh`). ### No secrets in the alias file System prompts are not secrets — they're user-authored instructions. API keys stay in `ai.env` (already managed by `pos config ai`). No new secret surface. --- ## Decision 9: Integration with pos-ai ### How aliases invoke pos-ai Each generated alias calls: ```bash alias ='pos ai ask --session --system ""' ``` This uses the existing `pos-ai` flags: - `--provider ` — supported since the beginning (line 633 of `bin/pos-ai`) - `--session ` — supported (line 639) - `--system ` — supported (line 642) **No changes to `bin/pos-ai` are required.** The alias tool is a standalone configuration tool that writes shell aliases calling `pos ai`. ### Provider validation The alias tool must validate the provider name against installed providers. It reuses the same discovery logic from `pos-ai`: ```bash PROVIDER_DIR="$(dirname "$0")/../lib/ai-providers" # Fallback for installed layout [ -d "$PROVIDER_DIR" ] || PROVIDER_DIR="$(dirname "$0")/ai-providers" ``` This is the same pattern used in `pos-ai` at line 17-18. The alias tool discovers providers independently (no dependency on `pos-ai` being sourced). --- ## Decision 10: Installation Changes ### postinstall.sh modification Add a block after the existing `ai.env` installation (around line 50): ```bash # ── AI aliases shell integration ─────────────────────────────── ALIAS_SRC_LINE='# AI aliases (managed by pos ai alias) [ -f ~/.config/linux_post_install/ai-aliases.sh ] && source ~/.config/linux_post_install/ai-aliases.sh' if ! grep -qsF "ai-aliases.sh" "$BASHRC" 2>/dev/null; then run printf '%s\n' "$ALIAS_SRC_LINE" >> "$BASHRC" log "Added AI aliases source to ~/.bashrc" fi ``` ### bin/pos modification Add `ai-alias` to the `INTERACTIVE_CMDS` list (line 262). Current value: ``` 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" ``` Add `ai-alias` to this space-separated list. ### No other installation changes - No new apt packages (no deps beyond bash) - No new lib files (tool sources `common.sh` + `menu-lib.sh` from existing libs) - No systemd services - No config template in `config/` --- ## Decision 11: Long Prompt Handling System prompts can be arbitrarily long. Shell aliases have a practical limit (ARG_MAX, typically 2MB on Linux), so this is not a hard constraint. However: - **Display:** `list` output truncates to 60 chars + `...` - **Storage:** Full prompt in `.env` and `.sh` — no truncation - **Interactive edit:** Shows full current value, allows full editing - **Warning:** During creation, if prompt exceeds 500 chars: `warn "System prompt is long (${#prompt} chars) — consider keeping it concise"` **Decision:** No artificial length limit. --- ## Implementation Phases ### Phase 1: Core tool (single commit) 1. Create `bin/pos-ai-alias` with: - Shebang, strict mode, `common.sh` source, `menu-lib.sh` source - `# POS:` header + `# POS_SUBCMDS:` - `CONFIG_FILE` and `ALIASES_SH_FILE` path constants - `_load_aliases()` — reads `.env` into parallel arrays (names, providers, sessions, prompts) - `_find_alias()` — lookup by name, returns index - `_write_env_file()` — writes entire `.env` from arrays - `_regen_aliases()` — reads `.env`, writes `.sh` with proper escaping - `_list_aliases()` — non-interactive table output - `_show_alias()` — non-interactive single alias details - `_create_alias()` — interactive create with validation - `_edit_alias()` — interactive edit with field-level prompts - `_remove_alias()` — interactive remove with confirm - `_main_menu()` — interactive menu via `menu_run` - Subcommand dispatch (`case` pattern) - `usage()` function 2. Add `ai-alias` to `INTERACTIVE_CMDS` in `bin/pos` 3. Add `.bashrc` source line to `postinstall.sh` ### Phase 2: Verification 1. `chmod +x bin/pos-ai-alias` 2. `bash -n bin/pos-ai-alias` 3. `make gen` — regenerate tables (new tool appears in dispatch table, bin tree, file table) 4. `make check` — self-consistency gate 5. `make lint` — convention gate (0 FAIL, 0 WARN) 6. Manual test: create, list, show, edit, remove aliases; verify `.sh` file is correct 7. Source `.bashrc` and verify aliases work ### Phase 3: Documentation 1. `DOC/POS.md` — add `ai alias` section (hand-written) 2. `DOC/HOWTO.md` — add index row 3. `DOC/howto/ai.md` — add aliases section (if ai.md exists; otherwise add to existing ai howto) 4. `DOC/AGENT_Context_Project.md` — regenerated by `make gen`; hand-add to Common Tasks table 5. Update `AGENT_TODO.md` Done section (dated) --- ## Architectural Constraints 1. **Tool must source `lib/common.sh`** via the fallback chain (not self-contained) 2. **Tool must source `lib/menu-lib.sh`** for interactive menus 3. **Tool MUST be in `INTERACTIVE_CMDS`** in `bin/pos` 4. **Generated `.sh` file must pass `bash -n`** before commit 5. **Env-seam:** all file paths use `${CONFIG_DIR:-...}` pattern (already in `common.sh`) 6. **`chmod 600`** for `.env`, `chmod 644` for `.sh` 7. **No dependency on `pos-ai`** being installed — tool writes config, doesn't run `pos ai` 8. **Pipe delimiter** — system prompts must not contain `|`; validated on input --- ## Verification Checklist - [ ] `bash -n bin/pos-ai-alias` passes - [ ] `shellcheck bin/pos-ai-alias` passes (or only known false positives) - [ ] `make gen` regenerates tables correctly - [ ] `make check` passes (bash -n, exec bits, doc sync, smoke) - [ ] `make lint` passes (0 FAIL, 0 WARN) - [ ] `pos ai alias --help` shows help - [ ] `pos ai alias` shows interactive menu - [ ] Create -> list -> show -> edit -> remove cycle works - [ ] Generated `.sh` file has correct alias syntax - [ ] `bash -n` on generated `.sh` passes - [ ] `.bashrc` source line works (aliases available after source) - [ ] Special chars in system prompt (single quotes, spaces, $) survive round-trip - [ ] Duplicate name is rejected - [ ] Invalid alias name is rejected - [ ] Invalid provider is rejected --- ## Risks and Mitigations | Risk | Likelihood | Impact | Mitigation | |------|------------|--------|------------| | Single-quote escaping fails for exotic prompts | Low | High (syntax error in .sh) | `bash -n` check before commit; warn + skip on failure | | User has many aliases -> list becomes long | Low | Low | `menu_pick` already supports filtering | | `.bashrc` source line conflicts with existing alias definitions | Very Low | Medium | Grep-check before adding; line is a conditional source, not an alias definition | | `make lint` rejects the new tool for a convention violation | Low | Low (blocking) | Follow template exactly; deps guards before help; proper header | --- **End of architecture document.**