feat: pos tree — print the CLI command tree
pos tree derives the hierarchy live from bin/pos-* filenames + # POS: /# POS_SUBCMDS: headers, so it always matches what the dispatcher can run. Category-less tool (like pos-config); --depth N limits nesting. Also sharpen the agent-facing docs that the work exposed: - AGENTS.md: drop the stale "one category-less exception" claim; document the category-less pattern (pos-config, pos-tree) and that gen only reads the text after the first em-dash in # POS: headers - DEV.md: category-less vs categorized rule + header extraction note - AGENT_Context "Adding a New Tool": new step 0 — pin the exact CLI verb and runtime context (dev/repo-only vs installed) before writing code - POS.md: new config + tree sections; category-less tools get their own sections - README: pos tree bullet following the pos X convention
This commit is contained in:
@@ -14,7 +14,7 @@ CRITICAL: real guidance lives in DOC/. When you encounter a reference below, use
|
||||
|
||||
## Quick facts
|
||||
|
||||
- **Tool model:** `bin/pos-<category>-<command>` (one category-less exception: `pos-config`). `bin/pos` dispatches by longest-prefix arg matching. New tools are auto-discovered but must be executable (`100755`) and carry a `# POS: <cat> <cmd> — <desc>` header right after the shebang; `# POS_FLAGS:` / `# POS_SUBCMDS:` / `# POS_CONFIG:` headers feed tab-completion and the `pos config` scope registry. A missing `# POS:` header hard-fails `make gen`.
|
||||
- **Tool model:** `bin/pos-<category>-<command>`, or **category-less** `bin/pos-<cmd>` for dispatcher/dev-level commands that fit no category (`pos-config`, `pos-tree`) — they dispatch like any tool and show with an empty category in the generated tables. `bin/pos` dispatches by longest-prefix arg matching. New tools are auto-discovered but must be executable (`100755`) and carry a `# POS: <cat> <cmd> — <desc>` header right after the shebang; `make gen` only uses the text after the first `— ` (the leading words are convention-only), so keep the one-line description concise. `# POS_FLAGS:` / `# POS_SUBCMDS:` / `# POS_CONFIG:` headers feed tab-completion and the `pos config` scope registry. A missing `# POS:` header hard-fails `make gen`.
|
||||
- **Generated code:** blocks between `GEN:START`/`GEN:END` markers in `DOC/AGENT_Context_Project.md` (tree, dispatch, selfcontained, filetable, docmap) and `completions/pos.bash` (flags, subcmds, config scopes) are `make gen` output — never hand-edit them. After touching `bin/pos-*`, run `make gen` then `make check` (bash -n + exec-bit check + doc-sync gate + dispatch smoke; definition of done). Hand-maintained, not gen-checked: `DOC/POS.md`, the line-count rows above the filetable marker (e.g. `lib/common.sh`), `bin/pos` usage() EXAMPLES, root README.
|
||||
- **Stdin gotcha:** any tool that reads stdin must be added to `INTERACTIVE_CMDS` in `bin/pos` — otherwise the logging `tee` pipe hangs on (or swallows) the prompt.
|
||||
- **Deps:** apt packages → `PACKAGES` array in `preinstall.sh`; non-apt/manual installers (e.g. `usbsrv`) → `command -v <bin> || err "…"` guard inside the tool, never in PACKAGES.
|
||||
|
||||
@@ -16,6 +16,8 @@ summary (newest last).
|
||||
|
||||
## Done
|
||||
|
||||
- **2026-08-09** — `pos tree`: prints the live `pos` command tree (categories → commands → subcommands) by deriving the hierarchy from `bin/pos-*` filenames + `# POS:` / `# POS_SUBCMDS:` headers, so it always matches what the dispatcher can run. Category-less like `pos-config`; `--depth N` limit; `pos help tree` works. Docs: POS.md `tree` section, `bin/pos` usage example, `make gen` regenerated the AGENT_Context tree/dispatch/filetable + `_pos_flags[tree]`.
|
||||
|
||||
- **2026-08-09** — Telegram sender `config` / `config set` removed — redundant with `pos config telegram` (same `# POS_CONFIG:` registry, masked token display + input, chat-id validation, chmod 600); sender/listener error hints now point there. Deep-review bugfixes in the same commit: mapped `/command` values containing `|` are no longer truncated (`load_map` switched from a `|` to a `\x1f` delimiter — previously `/up=echo hi | head` silently ran `echo hi `); `pos entertainment send <plugin> [args…]` actually forwards the extra args (every arg was `shift`ed in the flag loop, so `$@` was empty) and passes `--` before the message so leading-`-` plugin output isn't parsed as an option; `write_config_key` (entertainment-lib) and `cfg_write` (config-ui) replaced unescaped `sed -i "s|^K=.*|K=\"$v\"|"` with grep-v+append so values with `&`/`|`/`\` no longer mangle (also the path all telegram config now flows through); `sync_systemd` daemon-reloads after removing timer units; `digits` config validation accepts negative group/supergroup chat ids (`-100…`).
|
||||
|
||||
- **2026-08-09** — Fixed telegram listener editor crash on remove/edit/test: `ui_pick` printed its menu listing to **stdout**, so `idx="$(ui_pick)"` captured the menu *and* the number, and `MAP_CMDS[$idx]` (arithmetic array subscript) blew up with "syntax error in expression". Menu decoration now goes to stderr; only the picked index is emitted on stdout. Pre-existing bug (before the `::desc` work), exposed by the description column.
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
|
||||
<!-- GEN:START docmap -->
|
||||
| ## 1. Project Overview | 28–43 |
|
||||
| ## 2. Directory Structure | 44–180 |
|
||||
| ## 3. Installation Flow | 181–233 |
|
||||
| ## 4. The `pos` CLI System | 234–295 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 296–327 |
|
||||
| ## 6. Docker Compose / ScaleTail | 328–370 |
|
||||
| ## 7. Optional Apps (`apps/`) | 371–400 |
|
||||
| ## 8. Entertainment Module | 401–414 |
|
||||
| ## 9. Systemd Services | 415–427 |
|
||||
| ## 10. Configuration Files | 428–453 |
|
||||
| ## 11. Coding Conventions | 454–486 |
|
||||
| ## 12. Development Workflow | 487–538 |
|
||||
| ## 13. Key File Quick Reference | 539–586 |
|
||||
| ## 14. Common Tasks for Agents | 587–611 |
|
||||
| ## 2. Directory Structure | 44–181 |
|
||||
| ## 3. Installation Flow | 182–234 |
|
||||
| ## 4. The `pos` CLI System | 235–297 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 298–329 |
|
||||
| ## 6. Docker Compose / ScaleTail | 330–372 |
|
||||
| ## 7. Optional Apps (`apps/`) | 373–402 |
|
||||
| ## 8. Entertainment Module | 403–416 |
|
||||
| ## 9. Systemd Services | 417–429 |
|
||||
| ## 10. Configuration Files | 430–455 |
|
||||
| ## 11. Coding Conventions | 456–488 |
|
||||
| ## 12. Development Workflow | 489–541 |
|
||||
| ## 13. Key File Quick Reference | 542–590 |
|
||||
| ## 14. Common Tasks for Agents | 591–615 |
|
||||
<!-- GEN:END docmap -->
|
||||
|
||||
## 1. Project Overview
|
||||
@@ -82,6 +82,7 @@ Linux_post_install/
|
||||
│ ├── pos-system-health # Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL
|
||||
│ ├── pos-system-nfs-client # Mount NFS shares (ephemeral or persistent systemd mount units)
|
||||
│ ├── pos-system-nfs-server # Manage the NFS kernel server (status, share/unshare exports, enable/disable)
|
||||
│ ├── pos-tree # Show the pos CLI command tree: categories, commands, and subcommands
|
||||
│ ├── pos-usb-server # USB Redirector server control (--ls, --share; prompts when args omitted)
|
||||
<!-- GEN:END tree -->
|
||||
│ ├── flag-reader # Inspect feature flags (list/status/--raw)
|
||||
@@ -274,6 +275,7 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
|
||||
| system | health | `pos-system-health` | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL |
|
||||
| system | nfs-client | `pos-system-nfs-client` | Mount NFS shares (ephemeral or persistent systemd mount units) |
|
||||
| system | nfs-server | `pos-system-nfs-server` | Manage the NFS kernel server (status, share/unshare exports, enable/disable) |
|
||||
| | tree | `pos-tree` | Show the pos CLI command tree: categories, commands, and subcommands |
|
||||
| usb | server | `pos-usb-server` | USB Redirector server control (--ls, --share; prompts when args omitted) |
|
||||
<!-- GEN:END dispatch -->
|
||||
|
||||
@@ -502,7 +504,8 @@ System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
|
||||
|
||||
### Adding a New Tool
|
||||
|
||||
1. Create `bin/pos-<category>-<command>` from `templates/pos-tool.sh` — must be executable (`100755`); it auto-appears in `pos <category> --help` (filename-derived, no registration)
|
||||
0. Define the exact CLI verb (`pos <category> <command> [<subcommand>]`) and its runtime context before writing code: a **dev/repo-only** tool (e.g. reads repo files, like `pos tree`) or a **runtime** tool that must work from `/usr/local/bin` after the repo is deleted. Category-less `bin/pos-<cmd>` is for dispatcher/dev-level commands that fit no category (`pos-config`, `pos-tree`); everything else goes in a category.
|
||||
1. Create `bin/pos-<category>-<command>` (or `bin/pos-<cmd>` for category-less) from `templates/pos-tool.sh` — must be executable (`100755`); it auto-appears in `pos <category> --help` (filename-derived, no registration)
|
||||
2. Add the `# POS: <cat> <cmd> — <one-line description>` header right after the shebang (plus `# POS_FLAGS: ...` for flag-style tools and `# POS_SUBCMDS: ...` for multi-command tools) — this is the single source of truth for the generated docs; nested tools (`pos-<cat>-<a>-<b>`) auto-list under their parent tool
|
||||
3. Add to `INTERACTIVE_CMDS` in `bin/pos` if it reads stdin
|
||||
4. Add system deps to `PACKAGES` array in `preinstall.sh` (if needed); non-apt/manual installers → `command -v` guard in the tool instead
|
||||
@@ -552,7 +555,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/flag-clear` | 21 | Unset a flag |
|
||||
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
|
||||
<!-- GEN:START filetable -->
|
||||
| `bin/pos` | 270 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos` | 272 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos-communication-telegram-listener` | 509 | Telegram bot listener: map /command → bash, run them on chat messages |
|
||||
| `bin/pos-communication-telegram-sender` | 220 | Send Telegram messages/files/links/stickers via Bot API (send, test) |
|
||||
| `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) |
|
||||
@@ -577,8 +580,9 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/pos-system-health` | 209 | Host health dashboard (disk, RAM, services, backup age, fail2ban, docker); exit 1 if any FAIL |
|
||||
| `bin/pos-system-nfs-client` | 138 | Mount NFS shares (ephemeral or persistent systemd mount units) |
|
||||
| `bin/pos-system-nfs-server` | 134 | Manage the NFS kernel server (status, share/unshare exports, enable/disable) |
|
||||
| `bin/pos-tree` | 110 | Show the pos CLI command tree: categories, commands, and subcommands |
|
||||
| `bin/pos-usb-server` | 218 | USB Redirector server control (--ls, --share; prompts when args omitted) |
|
||||
| `completions/pos.bash` | 279 | Dynamic bash completion |
|
||||
| `completions/pos.bash` | 280 | Dynamic bash completion |
|
||||
<!-- GEN:END filetable -->
|
||||
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
|
||||
|
||||
|
||||
+2
-1
@@ -127,7 +127,8 @@ esac
|
||||
# POS_FLAGS: --flag1 --flag2 # ONLY for flag-style tools
|
||||
# POS_SUBCMDS: sub1 sub2 # ONLY for multi-command tools
|
||||
```
|
||||
The description feeds the dispatch table, bin tree and file table in `DOC/AGENT_Context_Project.md`; `POS_FLAGS` feeds flag completion and `POS_SUBCMDS` feeds subcommand completion in `completions/pos.bash` (both update via `make gen`).
|
||||
The description feeds the dispatch table, bin tree and file table in `DOC/AGENT_Context_Project.md`; `POS_FLAGS` feeds flag completion and `POS_SUBCMDS` feeds subcommand completion in `completions/pos.bash` (both update via `make gen`). `make gen` only reads the text after the first `— ` — the `<category> <command>` words before it are convention-only (for nested tools, keep the full path there, e.g. `# POS: communication telegram-listener — …`).
|
||||
- **Category-less vs categorized:** most tools are `bin/pos-<category>-<command>`. Use category-less `bin/pos-<cmd>` (e.g. `pos-config`, `pos-tree`) only for dispatcher/dev-level commands that fit no category — they dispatch and document like any tool but show with an empty category in the generated tables.
|
||||
- Nested tools (e.g. `bin/pos-communication-telegram-listener`) are auto-detected from filenames: the trailing segment (`listener`) is offered as a subcommand of the parent tool (`communication-telegram`) in `pos <category> --help` and tab-completion, instead of appearing as a flat sibling (`telegram-listener`). The flat dash-form (`pos communication telegram-listener`) still dispatches.
|
||||
- Optionally add an EXAMPLES line in `bin/pos` `usage()` to showcase the tool in `pos --help`.
|
||||
- If the command **reads stdin** (prompts/selection), add it to `INTERACTIVE_CMDS` in `bin/pos` — see [The `pos` CLI](#the-pos-cli).
|
||||
|
||||
+23
@@ -14,6 +14,8 @@
|
||||
- [communication](#communication)
|
||||
- [entertainment](#entertainment)
|
||||
- [flags](#flags)
|
||||
- [config](#config)
|
||||
- [tree](#tree)
|
||||
- [Legacy wrappers](#legacy-wrappers)
|
||||
|
||||
---
|
||||
@@ -48,6 +50,8 @@ Every non-interactive `pos` invocation logs to `~/.local/share/linux_post_instal
|
||||
|
||||
## Commands
|
||||
|
||||
Category-less tools (`config`, `tree`) live outside any category and are documented in their own `###` sections below.
|
||||
|
||||
### network
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
@@ -301,6 +305,25 @@ Feature-flag management CLIs (see [SCRIPTS.md → lib/flags.sh](SCRIPTS.md#libfl
|
||||
| `flag-set <name> [value]` | Set a flag, optionally with a value (requires sudo) |
|
||||
| `flag-clear <name>` | Unset a flag (requires sudo) |
|
||||
|
||||
### config
|
||||
|
||||
`pos config` is the interactive editor for the tools' runtime config (see [DEV.md](DEV.md#config-files) and §10 of AGENT_Context). Every tool exposes its configuration by declaring a `# POS_CONFIG:` header; `pos config` reads those at runtime — it knows nothing about the variables themselves. Values live in `~/.config/linux_post_install/<scope>.env` (chmod 600).
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `pos config` | Scope picker (on a TTY), otherwise the scope list |
|
||||
| `pos config <scope>` | Edit that scope's variables (masked secrets, validation, `-` to clear) |
|
||||
| `pos config <scope> set KEY=VALUE` | Set a value non-interactively (each tool's `config set` form) |
|
||||
|
||||
### tree
|
||||
|
||||
`pos tree` prints the `pos` command tree — every category, command, and subcommand the dispatcher can reach, annotated with each tool's `# POS:` description. Data is derived live from the `bin/pos-*` filenames and their `# POS_SUBCMDS:` headers, so it always matches what `pos` can actually run.
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `pos tree` | Full command tree |
|
||||
| `pos tree --depth N` | Limit nesting depth (1 = root only) |
|
||||
|
||||
---
|
||||
|
||||
## Legacy wrappers
|
||||
|
||||
@@ -13,6 +13,7 @@ It is a **personal toolkit** — a bootstrap script, a unified `pos` CLI for eve
|
||||
- 25+ system packages installed automatically
|
||||
- The `pos` CLI: network, Docker (compose + vbox VMs), media, system, SSH, USB, communication (Telegram), and entertainment (public-API plugins → Telegram) tools
|
||||
- Wi-Fi hotspot tools (`create_ap`, `wihotspot-gui`) via `pos network hotspot`
|
||||
- `pos tree` — browse the whole CLI: categories, commands, and subcommands
|
||||
- 15 optional desktop apps (VS Code, Brave, OBS, Tailscale, …) — pick what you want
|
||||
- 119+ self-hosted services with Tailscale access (Jellyfin, Home Assistant, …)
|
||||
- systemd services for SSH agent, boot-time automation, and a daily Telegram health digest (`pos system health`)
|
||||
|
||||
@@ -176,6 +176,8 @@ EXAMPLES
|
||||
pos config telegram Edit Telegram config interactively
|
||||
pos config Pick a config scope to edit
|
||||
|
||||
pos tree Show the command tree (categories + subcommands)
|
||||
|
||||
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
|
||||
|
||||
Executable
+110
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: tree — Show the pos CLI command tree: categories, commands, and subcommands
|
||||
# POS_FLAGS: --depth
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos tree [--depth N]
|
||||
|
||||
Print the `pos` command tree: every category, command, and subcommand the
|
||||
dispatcher can reach, annotated with each tool's # POS: description.
|
||||
|
||||
pos tree Full command tree
|
||||
pos tree --depth N Limit nesting depth (1 = root only)
|
||||
pos tree --help This help
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
depth=""
|
||||
case "${1:-}" in
|
||||
-h|--help) usage ;;
|
||||
--depth)
|
||||
[ $# -ge 2 ] || err "--depth requires a number"
|
||||
depth="$2"
|
||||
[[ "$depth" =~ ^[0-9]+$ ]] || err "--depth must be a number"
|
||||
shift 2
|
||||
;;
|
||||
-*) err "unknown flag: $1" ;;
|
||||
esac
|
||||
|
||||
self="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
declare -A children meta
|
||||
maxw=0
|
||||
|
||||
# add <parent> <word> <type> <desc> — parent "/" is the root.
|
||||
add() {
|
||||
local parent="$1" word="$2" type="$3" desc="$4" path
|
||||
if [ "$parent" = "/" ]; then path="$word"; else path="$parent/$word"; fi
|
||||
children["$parent"]+=" $word"
|
||||
meta["$path"]="$type|$desc"
|
||||
}
|
||||
|
||||
for f in "$self"/pos-*; do
|
||||
[ -x "$f" ] || continue
|
||||
name="${f##*/pos-}"
|
||||
desc="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$f")"
|
||||
desc="${desc#*— }"
|
||||
sc="$(sed -n '/^# POS_SUBCMDS: /{s/^# POS_SUBCMDS: //;p;q}' "$f")"
|
||||
words=(${name//-/ })
|
||||
parent="/"
|
||||
for ((i=0; i<${#words[@]}; i++)); do
|
||||
word="${words[$i]}"
|
||||
if [ "$parent" = "/" ]; then path="$word"; else path="$parent/$word"; fi
|
||||
if [ $((i + 1)) -eq ${#words[@]} ]; then
|
||||
add "$parent" "$word" "tool" "$desc"
|
||||
else
|
||||
[ -n "${meta[$path]:-}" ] || add "$parent" "$word" "cat" ""
|
||||
fi
|
||||
parent="$path"
|
||||
done
|
||||
for s in $sc; do
|
||||
add "$parent" "$s" "sub" ""
|
||||
done
|
||||
done
|
||||
|
||||
# pad = widest "pos <full path>" leaf, so descriptions align
|
||||
for p in "${!meta[@]}"; do
|
||||
case "${meta[$p]%%|*}" in
|
||||
tool|sub)
|
||||
len=$(( ${#p} + 4 ))
|
||||
[ "$len" -gt "$maxw" ] && maxw=$len
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
render() {
|
||||
local parent="$1" prefix="$2" lvl="$3"
|
||||
if [ -n "$depth" ] && [ "$lvl" -ge "$depth" ]; then return; fi
|
||||
local -a kids
|
||||
mapfile -t kids < <(printf '%s\n' ${children[$parent]:-} | sort -u)
|
||||
local i word path type desc conn nextpref last
|
||||
for ((i=0; i<${#kids[@]}; i++)); do
|
||||
word="${kids[$i]}"
|
||||
if [ "$parent" = "/" ]; then path="$word"; else path="$parent/$word"; fi
|
||||
last=$(( i == ${#kids[@]} - 1 ))
|
||||
if [ "$last" -eq 1 ]; then conn="└── "; nextpref="$prefix "; else conn="├── "; nextpref="$prefix│ "; fi
|
||||
type="${meta[$path]%%|*}"
|
||||
desc="${meta[$path]#*|}"
|
||||
case "$type" in
|
||||
cat)
|
||||
printf '%s%s%s/\n' "$prefix" "$conn" "$word"
|
||||
render "$path" "$nextpref" $((lvl+1))
|
||||
;;
|
||||
tool)
|
||||
printf '%s%s%-*s# %s\n' "$prefix" "$conn" "$((maxw+1))" "pos ${path//\// }" "$desc"
|
||||
[ -n "${children[$path]:-}" ] && render "$path" "$nextpref" $((lvl+1))
|
||||
;;
|
||||
sub)
|
||||
printf '%s%s%s\n' "$prefix" "$conn" "pos ${path//\// }"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
printf 'pos\n'
|
||||
render "/" "" 1
|
||||
@@ -8,6 +8,7 @@ _pos_flags[communication-telegram-sender]="--type --caption --parse-mode --no-pr
|
||||
_pos_flags[entertainment-send]="--print --markdown"
|
||||
_pos_flags[network-hotspot]="--foreground"
|
||||
_pos_flags[system-backup]="--service"
|
||||
_pos_flags[tree]="--depth"
|
||||
_pos_flags[usb-server]="--ls --ls-shared --share --unshare --auto-share --callback --close-callback --auto-connect --disconnect --nickname --timeout --port --info --version"
|
||||
# GEN:END posflags
|
||||
# GEN:START possubcmds
|
||||
|
||||
Reference in New Issue
Block a user